From bf198a34aaf822aa902b0bece5b9bb5e16bdea6d Mon Sep 17 00:00:00 2001 From: Sergey Tarnavskiy Date: Wed, 25 Sep 2019 19:52:12 +0300 Subject: [PATCH 001/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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/261] 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 f8bb2839aa5399d7db91ac4249050c70ee5614ea Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 15 Oct 2019 17:11:59 +0300 Subject: [PATCH 017/261] Add support import label --- ui/src/app/api/entity.service.js | 3 ++- ui/src/app/common/types.constant.js | 4 ++++ ui/src/app/import-export/import-dialog-csv.controller.js | 6 +++++- .../app/import-export/table-columns-assignment.directive.js | 6 ++++++ ui/src/app/import-export/table-columns-assignment.tpl.html | 1 + ui/src/app/locale/locale.constant-en_US.json | 1 + ui/src/app/locale/locale.constant-ru_RU.json | 1 + ui/src/app/locale/locale.constant-uk_UA.json | 1 + 8 files changed, 21 insertions(+), 2 deletions(-) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index f39615c933..f474ff4f42 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -1130,7 +1130,8 @@ 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) { 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 12:49:02 +0300 Subject: [PATCH 018/261] 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 019/261] 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 020/261] 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 021/261] 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 022/261] 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 023/261] 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 024/261] 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 025/261] 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 026/261] 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 027/261] 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 028/261] 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 029/261] 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 030/261] 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 031/261] 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 032/261] 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 033/261] 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 034/261] 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 035/261] 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 036/261] 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 037/261] 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 038/261] 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 039/261] 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 040/261] 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 041/261] 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 042/261] 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 043/261] 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 044/261] 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 045/261] 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 046/261] 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 047/261] 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 048/261] 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 049/261] 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 050/261] 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 051/261] 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 052/261] 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 053/261] 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 054/261] 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 055/261] 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 056/261] 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 057/261] 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 058/261] 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 059/261] 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 060/261] 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 061/261] 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 062/261] 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 063/261] 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 064/261] 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 065/261] 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 066/261] 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 067/261] 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 068/261] 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 069/261] 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 070/261] 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 071/261] 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 072/261] 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 073/261] 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 074/261] 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 075/261] 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 076/261] 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 077/261] 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 078/261] 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 079/261] 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 080/261] 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 081/261] 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 082/261] 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 083/261] [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 084/261] 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 085/261] 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 086/261] 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 087/261] 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 088/261] 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 089/261] 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 090/261] 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 091/261] 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 092/261] 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 093/261] 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 094/261] 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 095/261] 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 096/261] 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 097/261] 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 098/261] 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 099/261] 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 100/261] 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 101/261] 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 102/261] 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 103/261] 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 104/261] 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 105/261] 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 106/261] 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 107/261] 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 108/261] 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 109/261] 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 110/261] 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 dd6474907563ea039fece0d313666ebc66f2d27f Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Sun, 8 Dec 2019 23:52:29 +0200 Subject: [PATCH 111/261] init commit --- .../CassandraDatabaseUpgradeService.java | 7 ++++ .../install/SqlDatabaseUpgradeService.java | 3 ++ .../server/common/data/alarm/Alarm.java | 4 +++ .../server/dao/alarm/BaseAlarmService.java | 33 ++++++++++++++++--- .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/nosql/AlarmEntity.java | 20 +++++++++++ .../server/dao/model/sql/AlarmEntity.java | 17 ++++++++++ .../resources/cassandra/schema-entities.cql | 1 + .../main/resources/sql/schema-entities.sql | 1 + .../rule/engine/action/TbCreateAlarmNode.java | 25 ++++++++++++-- .../TbCreateAlarmNodeConfiguration.java | 8 ++++- 11 files changed, 113 insertions(+), 7 deletions(-) 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 68ca728823..4732ef6be7 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 @@ -270,9 +270,16 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { case "2.4.1": log.info("Updating schema ..."); String updateAssetTableStmt = "alter table asset add label text"; + String updateAlarmTableStmt = "alter table alarm add propagate_relation_types text"; try { + log.info("Updating assets ..."); cluster.getSession().execute(updateAssetTableStmt); Thread.sleep(2500); + log.info("Assets updated."); + log.info("Updating alarms ..."); + cluster.getSession().execute(updateAlarmTableStmt); + Thread.sleep(2500); + log.info("Alarms updated."); } catch (InvalidQueryException e) {} log.info("Schema updated."); break; 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 3210742388..1ded097e52 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 @@ -193,6 +193,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { 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) {} + try { + conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_relation_types varchar"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) {} log.info("Schema updated."); } break; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 065321ff91..b96f86b715 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import java.util.List; + /** * Created by ashvayka on 11.05.17. */ @@ -45,6 +47,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId { private long clearTs; private transient JsonNode details; private boolean propagate; + private List propagateRelationTypes; public Alarm() { super(); @@ -68,6 +71,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId { this.clearTs = alarm.getClearTs(); this.details = alarm.getDetails(); this.propagate = alarm.isPropagate(); + this.propagateRelationTypes = alarm.getPropagateRelationTypes(); } @Override 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 8a4279fe7f..fa5ce6d4b4 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 @@ -23,8 +23,8 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; @@ -52,6 +52,7 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; +import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; @@ -59,6 +60,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -154,8 +156,16 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private List getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException { EntityRelationsQuery query = new EntityRelationsQuery(); - query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false)); - return relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList()); + RelationsSearchParameters parameters = new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false); + query.setParameters(parameters); + List propagateRelationTypes = alarm.getPropagateRelationTypes(); + if (!CollectionUtils.isEmpty(propagateRelationTypes)) { + return relationService.findByQuery(alarm.getTenantId(), query).get().stream() + .filter(entityRelation -> propagateRelationTypes.contains(entityRelation.getType())) + .map(EntityRelation::getFrom).collect(Collectors.toList()); + } else { + return relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList()); + } } private ListenableFuture updateAlarm(Alarm update) { @@ -360,13 +370,28 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ existing.setSeverity(alarm.getSeverity()); existing.setDetails(alarm.getDetails()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); + List existingPropagateRelationTypes = existing.getPropagateRelationTypes(); + List newRelationTypes = alarm.getPropagateRelationTypes(); + if (!CollectionUtils.isEmpty(existingPropagateRelationTypes) && !CollectionUtils.isEmpty(newRelationTypes)) { + existing.setPropagateRelationTypes(Stream.concat(existingPropagateRelationTypes.stream(), newRelationTypes.stream()) + .distinct() + .collect(Collectors.toList())); + } else { + existing.setPropagateRelationTypes(Collections.emptyList()); + } return existing; } private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) { try { List relations = relationService.findByToAsync(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM).get(); - Set parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); + Set parents; + List propagateRelationTypes = alarm.getPropagateRelationTypes(); + if (!CollectionUtils.isEmpty(propagateRelationTypes)) { + parents = relations.stream().filter(entityRelation -> propagateRelationTypes.contains(entityRelation.getType())).map(EntityRelation::getFrom).collect(Collectors.toSet()); + } else { + parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); + } for (EntityId parentId : parents) { updateAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), oldStatus, newStatus); } 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 c466c034fc..4605b405ca 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 @@ -231,6 +231,7 @@ public class ModelConstants { public static final String ALARM_ACK_TS_PROPERTY = "ack_ts"; public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; + public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types"; public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java index 807a73fe38..e274ca3348 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java @@ -23,6 +23,8 @@ import com.datastax.driver.mapping.annotations.Table; import com.fasterxml.jackson.databind.JsonNode; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmId; @@ -36,6 +38,7 @@ import org.thingsboard.server.dao.model.type.AlarmStatusCodec; import org.thingsboard.server.dao.model.type.EntityTypeCodec; import org.thingsboard.server.dao.model.type.JsonCodec; +import java.util.Arrays; import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; @@ -46,6 +49,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPE import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; @@ -102,6 +106,9 @@ public final class AlarmEntity implements BaseEntity { @Column(name = ALARM_PROPAGATE_PROPERTY) private Boolean propagate; + @Column(name = ALARM_PROPAGATE_RELATION_TYPES) + private String propagateRelationTypes; + public AlarmEntity() { super(); } @@ -125,6 +132,12 @@ public final class AlarmEntity implements BaseEntity { this.ackTs = alarm.getAckTs(); this.clearTs = alarm.getClearTs(); this.details = alarm.getDetails(); + this.details = alarm.getDetails(); + if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) { + this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes()); + } else { + this.propagateRelationTypes = null; + } } public UUID getId() { @@ -231,6 +244,10 @@ public final class AlarmEntity implements BaseEntity { this.propagate = propagate; } + public String getPropagateRelationTypes() { return propagateRelationTypes; } + + public void setPropagateRelationTypes(String propagateRelationTypes) { this.propagateRelationTypes = propagateRelationTypes; } + @Override public Alarm toData() { Alarm alarm = new Alarm(new AlarmId(id)); @@ -248,6 +265,9 @@ public final class AlarmEntity implements BaseEntity { alarm.setAckTs(ackTs); alarm.setClearTs(clearTs); alarm.setDetails(details); + if(!StringUtils.isEmpty(propagateRelationTypes)) { + alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(","))); + } return alarm; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java index f6cfb7189c..03b6218be9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java @@ -21,6 +21,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.alarm.Alarm; @@ -40,6 +42,8 @@ import javax.persistence.EnumType; import javax.persistence.Enumerated; import javax.persistence.Table; +import java.util.Arrays; + import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; @@ -47,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPE import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; @@ -99,6 +104,9 @@ public final class AlarmEntity extends BaseSqlEntity implements BaseEntit @Column(name = ALARM_PROPAGATE_PROPERTY) private Boolean propagate; + @Column(name = ALARM_PROPAGATE_RELATION_TYPES) + private String propagateRelationTypes; + public AlarmEntity() { super(); } @@ -122,6 +130,12 @@ public final class AlarmEntity extends BaseSqlEntity implements BaseEntit this.ackTs = alarm.getAckTs(); this.clearTs = alarm.getClearTs(); this.details = alarm.getDetails(); + if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) { + this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes()); + } else { + this.propagateRelationTypes = null; + } + } @Override @@ -141,6 +155,9 @@ public final class AlarmEntity extends BaseSqlEntity implements BaseEntit alarm.setAckTs(ackTs); alarm.setClearTs(clearTs); alarm.setDetails(details); + if(!StringUtils.isEmpty(propagateRelationTypes)) { + alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(","))); + } return alarm; } diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql index a07e27cbe1..5a07c4f2ad 100644 --- a/dao/src/main/resources/cassandra/schema-entities.cql +++ b/dao/src/main/resources/cassandra/schema-entities.cql @@ -306,6 +306,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.alarm ( clear_ts bigint, details text, propagate boolean, + propagate_relation_types text, PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) ) WITH CLUSTERING ORDER BY ( type ASC, id DESC); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 4b21d851a5..087240ec33 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS alarm ( start_ts bigint, status varchar(255), tenant_id varchar(31), + propagate_relation_types varchar, type varchar(255) ); 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 d6f6236311..e17b65be72 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 @@ -21,11 +21,11 @@ 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.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; 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.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.TenantId; @@ -33,6 +33,8 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.io.IOException; +import java.util.Collections; +import java.util.List; @Slf4j @RuleNode( @@ -56,7 +58,11 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes = this.config.getRelationTypes(); + if (relationTypes == null) { + relationTypes = Collections.emptyList(); + } return Alarm.builder() .tenantId(tenantId) .originator(msg.getOriginator()) @@ -145,6 +165,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode { @@ -26,18 +29,21 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura private boolean propagate; private boolean useMessageAlarmData; + private List relationTypes; + @Override public TbCreateAlarmNodeConfiguration defaultConfiguration() { TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); configuration.setAlarmDetailsBuildJs("var details = {};\n" + "if (metadata.prevAlarmDetails) {\n" + " details = JSON.parse(metadata.prevAlarmDetails);\n" + - "}\n"+ + "}\n" + "return details;"); configuration.setAlarmType("General Alarm"); configuration.setSeverity(AlarmSeverity.CRITICAL); configuration.setPropagate(false); configuration.setUseMessageAlarmData(false); + configuration.setRelationTypes(Collections.emptyList()); return configuration; } } From fc986a5b64c7d86dbe8d1c55fd62e58f77ee154e Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Fri, 6 Dec 2019 15:14:17 +0100 Subject: [PATCH 112/261] 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 113/261] 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 114/261] 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 115/261] 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 116/261] 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 117/261] 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 118/261] 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 119/261] 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 120/261] 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 f9c65d709db9cfcaef40ffa8b3a8dbb6a394ff76 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Mon, 9 Dec 2019 15:57:26 +0200 Subject: [PATCH 121/261] code update --- .../server/dao/alarm/BaseAlarmService.java | 14 +++++++------ .../rule/engine/action/TbCreateAlarmNode.java | 20 +++---------------- 2 files changed, 11 insertions(+), 23 deletions(-) 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 fa5ce6d4b4..6d3af6c118 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 @@ -372,12 +372,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); List existingPropagateRelationTypes = existing.getPropagateRelationTypes(); List newRelationTypes = alarm.getPropagateRelationTypes(); - if (!CollectionUtils.isEmpty(existingPropagateRelationTypes) && !CollectionUtils.isEmpty(newRelationTypes)) { - existing.setPropagateRelationTypes(Stream.concat(existingPropagateRelationTypes.stream(), newRelationTypes.stream()) - .distinct() - .collect(Collectors.toList())); - } else { - existing.setPropagateRelationTypes(Collections.emptyList()); + if (!CollectionUtils.isEmpty(newRelationTypes)) { + if(!CollectionUtils.isEmpty(existingPropagateRelationTypes)) { + existing.setPropagateRelationTypes(Stream.concat(existingPropagateRelationTypes.stream(), newRelationTypes.stream()) + .distinct() + .collect(Collectors.toList())); + } else { + existing.setPropagateRelationTypes(newRelationTypes); + } } return existing; } 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 e17b65be72..06352d9241 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 @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import java.io.IOException; -import java.util.Collections; import java.util.List; @Slf4j @@ -55,13 +54,12 @@ import java.util.List; public class TbCreateAlarmNode extends TbAbstractAlarmNode { private static ObjectMapper mapper = new ObjectMapper(); + private List relationTypes; @Override protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { TbCreateAlarmNodeConfiguration nodeConfiguration = TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class); - if(nodeConfiguration.getRelationTypes() == null) { - nodeConfiguration.setRelationTypes(Collections.emptyList()); - } + relationTypes = nodeConfiguration.getRelationTypes(); return nodeConfiguration; } @@ -72,7 +70,6 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes = this.config.getRelationTypes(); - if (relationTypes == null) { - relationTypes = Collections.emptyList(); - } return Alarm.builder() .tenantId(tenantId) .originator(msg.getOriginator()) From 752b3d90ab079c0916d78774eb9646699a1ba8af Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 10 Dec 2019 11:35:56 +0200 Subject: [PATCH 122/261] 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 123/261] 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 From f9abf1c7a24d4097a0432a9ef07cbd7417925d37 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 10 Dec 2019 12:50:20 +0200 Subject: [PATCH 124/261] Improved reporting of last activity time from remote transport --- .../service/AbstractTransportService.java | 24 +++++++++++++++---- .../transport/service/SessionMetaData.java | 1 + 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java index d8912c18fb..b09e94202d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Random; import java.util.UUID; import java.util.concurrent.*; @@ -176,10 +177,23 @@ public abstract class AbstractTransportService implements TransportService { sessions.remove(uuid); sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); } else { - process(sessionMD.getSessionInfo(), TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) - .setRpcSubscription(sessionMD.isSubscribedToRPC()) - .setLastActivityTime(sessionMD.getLastActivityTime()).build(), null); + if (sessionMD.getLastActivityTime() > sessionMD.getLastReportedActivityTime()) { + final long lastActivityTime = sessionMD.getLastActivityTime(); + process(sessionMD.getSessionInfo(), TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) + .setRpcSubscription(sessionMD.isSubscribedToRPC()) + .setLastActivityTime(sessionMD.getLastActivityTime()).build(), new TransportServiceCallback() { + @Override + public void onSuccess(Void msg) { + sessionMD.setLastReportedActivityTime(lastActivityTime); + } + + @Override + public void onError(Throwable e) { + log.warn("[{}] Failed to report last activity time", uuid, e); + } + }); + } } }); } @@ -288,7 +302,7 @@ public abstract class AbstractTransportService implements TransportService { } this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(); this.transportCallbackExecutor = Executors.newWorkStealingPool(20); - this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, sessionReportTimeout, sessionReportTimeout, TimeUnit.MILLISECONDS); + this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); } public void destroy() { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java index 411bbd5b72..d60d8fe624 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java @@ -34,6 +34,7 @@ class SessionMetaData { private ScheduledFuture scheduledFuture; private volatile long lastActivityTime; + private volatile long lastReportedActivityTime; private volatile boolean subscribedToAttributes; private volatile boolean subscribedToRPC; From c84a8a5e672b3934a30499b1baf3363b80f9a476 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Dec 2019 15:25:03 +0200 Subject: [PATCH 125/261] Version set to 2.5.0-SNAPSHOT --- 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.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.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.json | 2 +- ui/pom.xml | 2 +- 37 files changed, 38 insertions(+), 38 deletions(-) diff --git a/application/pom.xml b/application/pom.xml index 68ae1aa77c..06e2437417 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard application diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index b2a8cd467e..8509364af2 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/common/data/pom.xml b/common/data/pom.xml index 155a00b7f9..e88efd9cd5 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 60702c0801..ad7c8146df 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index ce20b753ba..a748481f9d 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 7821051137..36f2597a8e 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 26d478b596..30f2f1b66c 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 644df5a7b4..a0c25da472 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index ef0ae8eabd..09a517a666 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index ca36845902..405b75f175 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 88bc2f64d8..dc6cbfd1ba 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/util/pom.xml b/common/util/pom.xml index c695ac573e..a126dbb451 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT common org.thingsboard.common diff --git a/dao/pom.xml b/dao/pom.xml index 5e527a5aad..5ad7de4978 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard dao diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index a6b5d65ff7..fac58891d8 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index da4eb09b09..0cdafffef1 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.2", + "version": "2.5.0", "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 27636dbecd..3bfd3acfbb 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index 84d00e2d88..f9345f9419 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 8a2bab084a..820779b03e 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index f653fb7cab..ac9da0cca2 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index b606ba29fd..1b23d84b5a 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index c38756d5cd..b36ef5c0cf 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 7402d2e209..bb95512b1a 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.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index f869e02f18..84777e05c8 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index ce82272a26..176791d65a 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.2", + "version": "2.5.0", "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 c7bcf39caf..12d3040651 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 869eb2abb0..ef9e905839 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,12 +19,12 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard org.thingsboard netty-mqtt - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index 80038123ff..898c417300 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT pom Thingsboard diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 4197f275b6..0ceb8ed0d8 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 7253dcf483..312c1b2766 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.2-SNAPSHOT + 2.5.0-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 627aa42ab3..62901d4e18 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.2-SNAPSHOT + 2.5.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/tools/pom.xml b/tools/pom.xml index b192c1bf38..6a9644d003 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 08629c2a5e..2ae71754d6 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 8722273815..6686bbc659 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index badb11f6cd..e5b7250888 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index c830bbc009..5cf5d295af 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard transport diff --git a/ui/package.json b/ui/package.json index bb7c1b4979..4c67341178 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard", "private": true, - "version": "2.4.2", + "version": "2.5.0", "description": "ThingsBoard UI", "licenses": [ { diff --git a/ui/pom.xml b/ui/pom.xml index f037ccfde6..75bed7519d 100644 --- a/ui/pom.xml +++ b/ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 2.4.2-SNAPSHOT + 2.5.0-SNAPSHOT thingsboard org.thingsboard From 88434dec27a5c318701fdbce4d17af4b7e041a19 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 11 Dec 2019 13:57:39 +0200 Subject: [PATCH 126/261] Improvement alarms widget limit (#2257) * Add support import label * Add chunk size * Refactoring and add template to max limit * Add max number load alarms --- ui/src/app/api/alarm.service.js | 37 +++++++-- ui/src/app/api/subscription.js | 6 ++ .../widget/widget-config.directive.js | 8 +- .../components/widget/widget-config.tpl.html | 75 +++++++++++++------ .../components/widget/widget.controller.js | 4 + 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 | 10 ++- 8 files changed, 120 insertions(+), 36 deletions(-) diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js index 03269fe333..f1b0513ec6 100644 --- a/ui/src/app/api/alarm.service.js +++ b/ui/src/app/api/alarm.service.js @@ -183,7 +183,7 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { return deferred.promise; } - function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { + function fetchAlarms(alarmsQuery, pageLink, deferred, leftToLoad, alarmsList) { getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus, alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then( @@ -192,8 +192,19 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { alarmsList = []; } alarmsList = alarmsList.concat(alarms.data); + if (angular.isDefined(leftToLoad)) { + leftToLoad -= pageLink.limit; + if (leftToLoad === 0) { + alarmsList = $filter('orderBy')(alarmsList, ['-createdTime']); + deferred.resolve(alarmsList); + return; + } + if (leftToLoad < pageLink.limit) { + alarms.nextPageLink.limit = leftToLoad; + } + } if (alarms.hasNext && !alarmsQuery.limit) { - fetchAlarms(alarmsQuery, alarms.nextPageLink, deferred, alarmsList); + fetchAlarms(alarmsQuery, alarms.nextPageLink, deferred, leftToLoad, alarmsList); } else { alarmsList = $filter('orderBy')(alarmsList, ['-createdTime']); deferred.resolve(alarmsList); @@ -209,26 +220,34 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { var deferred = $q.defer(); var time = Date.now(); var pageLink; + var leftToLoad; if (alarmsQuery.limit) { pageLink = { limit: alarmsQuery.limit }; } else if (alarmsQuery.interval) { pageLink = { - limit: 100, + limit: alarmsQuery.alarmsFetchSize || 100, startTime: time - alarmsQuery.interval }; } else if (alarmsQuery.startTime) { pageLink = { - limit: 100, + limit: alarmsQuery.alarmsFetchSize || 100, startTime: Math.round(alarmsQuery.startTime) - } + }; if (alarmsQuery.endTime) { pageLink.endTime = Math.round(alarmsQuery.endTime); } } - fetchAlarms(alarmsQuery, pageLink, deferred); + if (angular.isDefined(alarmsQuery.alarmsMaxCountLoad) && alarmsQuery.alarmsMaxCountLoad !== 0) { + leftToLoad = alarmsQuery.alarmsMaxCountLoad; + if (leftToLoad < pageLink.limit) { + pageLink.limit = leftToLoad; + } + } + + fetchAlarms(alarmsQuery, pageLink, deferred, leftToLoad); return deferred.promise; } @@ -276,8 +295,10 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { entityType: alarmSource.entityType, entityId: alarmSource.entityId, alarmSearchStatus: alarmSourceListener.alarmSearchStatus, - alarmStatus: null - } + alarmStatus: null, + alarmsMaxCountLoad: alarmSourceListener.alarmsMaxCountLoad, + alarmsFetchSize: alarmSourceListener.alarmsFetchSize + }; var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); if (originatorKeys && originatorKeys.length) { alarmSourceListener.alarmsQuery.fetchOriginator = true; diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 728af2d38f..89f8aae309 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -75,6 +75,10 @@ export default class Subscription { options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any; this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ? options.alarmsPollingInterval : 5000; + this.alarmsMaxCountLoad = angular.isDefined(options.alarmsMaxCountLoad) ? + options.alarmsMaxCountLoad : 0; + this.alarmsFetchSize = angular.isDefined(options.alarmsFetchSize) ? + options.alarmsFetchSize : 100; this.alarmSourceListener = null; this.alarms = []; @@ -915,6 +919,8 @@ export default class Subscription { alarmSource: this.alarmSource, alarmSearchStatus: this.alarmSearchStatus, alarmsPollingInterval: this.alarmsPollingInterval, + alarmsMaxCountLoad: this.alarmsMaxCountLoad, + alarmsFetchSize: this.alarmsFetchSize, alarmsUpdated: function(alarms, apply) { subscription.alarmsUpdated(alarms, apply); } diff --git a/ui/src/app/components/widget/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js index 5c0b704eb1..470726a835 100644 --- a/ui/src/app/components/widget/widget-config.directive.js +++ b/ui/src/app/components/widget/widget-config.directive.js @@ -173,6 +173,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout config.alarmSearchStatus : types.alarmSearchStatus.any; scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ? config.alarmsPollingInterval : 5; + scope.alarmsMaxCountLoad = angular.isDefined(config.alarmsMaxCountLoad) ? + config.alarmsMaxCountLoad : 0; + scope.alarmsFetchSize = angular.isDefined(config.alarmsFetchSize) ? + config.alarmsFetchSize : 100; if (config.alarmSource) { scope.alarmSource.value = config.alarmSource; } else { @@ -243,7 +247,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout scope.$watch('title + showTitleIcon + titleIcon + iconColor + iconSize + titleTooltip + showTitle + dropShadow + enableFullscreen + backgroundColor + ' + 'color + padding + margin + widgetStyle + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' + - 'displayTimewindow + alarmSearchStatus + alarmsPollingInterval + showLegend', function () { + 'displayTimewindow + alarmSearchStatus + alarmsPollingInterval + alarmsMaxCountLoad + alarmsFetchSize + showLegend', function () { if (ngModelCtrl.$viewValue) { var value = ngModelCtrl.$viewValue; if (value.config) { @@ -277,6 +281,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout config.displayTimewindow = scope.displayTimewindow; config.alarmSearchStatus = scope.alarmSearchStatus; config.alarmsPollingInterval = scope.alarmsPollingInterval; + config.alarmsMaxCountLoad = scope.alarmsMaxCountLoad; + config.alarmsFetchSize = scope.alarmsFetchSize; config.showLegend = scope.showLegend; } if (value.layout) { diff --git a/ui/src/app/components/widget/widget-config.tpl.html b/ui/src/app/components/widget/widget-config.tpl.html index e5ac7b62b2..c5f6c298c7 100644 --- a/ui/src/app/components/widget/widget-config.tpl.html +++ b/ui/src/app/components/widget/widget-config.tpl.html @@ -37,29 +37,58 @@ is-edit="true" flex ng-model="timewindow">
-
- - - - - {{ ('alarm.search-status.' + searchStatus) | translate }} - - - - - - -
-
alarm.polling-interval-required
-
alarm.min-polling-interval-message
-
-
+
+
+ + + + + {{ ('alarm.search-status.' + searchStatus) | translate }} + + + + + + +
+
alarm.polling-interval-required
+
alarm.min-polling-interval-message
+
+
+
+
+ + + +
+
alarm.max-count-load-required
+
alarm.max-count-load-error-min
+
+
+ + + +
+
alarm.fetch-size-required
+
alarm.fetch-size-error-min
+
+
+
Date: Wed, 11 Dec 2019 14:27:13 +0200 Subject: [PATCH 127/261] Async JS Execution --- .../script/RuleNodeJsScriptEngine.java | 5 +++ .../rule/engine/api/ScriptEngine.java | 2 + .../engine/action/TbAbstractAlarmNode.java | 15 ++++++-- .../rule/engine/action/TbAlarmNodeTest.java | 38 +++---------------- 4 files changed, 24 insertions(+), 36 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 12150b806a..8b08333d3b 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 @@ -139,6 +139,11 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return executeScript(msg); } + @Override + public ListenableFuture executeJsonAsync(TbMsg msg) throws ScriptException { + return executeScriptAsync(msg); + } + @Override public String executeToString(TbMsg msg) throws ScriptException { JsonNode result = executeScript(msg); 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 f72adf04ab..1ffaabd6d1 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 @@ -38,6 +38,8 @@ public interface ScriptEngine { JsonNode executeJson(TbMsg msg) throws ScriptException; + ListenableFuture executeJsonAsync(TbMsg msg) throws ScriptException; + String executeToString(TbMsg msg) throws ScriptException; void destroy(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 5fc482348d..b8fbeaf74e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.action; import com.fasterxml.jackson.databind.JsonNode; 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; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -27,6 +28,9 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.script.ScriptException; + import static org.thingsboard.common.util.DonAsynchron.withCallback; @@ -67,21 +71,24 @@ public abstract class TbAbstractAlarmNode ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + t -> ctx.tellFailure(msg, t) + , ctx.getDbCallbackExecutor()); } protected abstract ListenableFuture processAlarm(TbContext ctx, TbMsg msg); protected ListenableFuture buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { - return ctx.getJsExecutor().executeAsync(() -> { + try { TbMsg dummyMsg = msg; if (previousDetails != null) { TbMsgMetaData metaData = msg.getMetaData().copy(); metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails)); dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData()); } - return buildDetailsJsEngine.executeJson(dummyMsg); - }); + return buildDetailsJsEngine.executeJsonAsync(dummyMsg); + } catch (Exception e) { + return Futures.immediateFailedFuture(e); + } } private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) { 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 fb07951368..1884ff2330 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 @@ -26,6 +26,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.common.util.ListeningExecutor; @@ -60,8 +61,6 @@ public class TbAlarmNodeTest { @Mock private TbContext ctx; @Mock - private ListeningExecutor executor; - @Mock private AlarmService alarmService; @Mock @@ -102,7 +101,7 @@ public class TbAlarmNodeTest { metaData.putValue("key", "value"); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); - when(detailsJs.executeJson(msg)).thenReturn(null); + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); @@ -136,8 +135,6 @@ public class TbAlarmNodeTest { .build(); assertEquals(expectedAlarm, actualAlarm); - - verify(executor, times(1)).executeAsync(any(Callable.class)); } @Test @@ -146,7 +143,7 @@ public class TbAlarmNodeTest { metaData.putValue("key", "value"); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); - when(detailsJs.executeJson(msg)).thenThrow(new NotImplementedException("message")); + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message"))); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); node.onMsg(ctx, msg); @@ -154,7 +151,6 @@ public class TbAlarmNodeTest { verifyError(msg, "message", NotImplementedException.class); verify(ctx).createJsScriptEngine("DETAILS"); - verify(ctx, times(1)).getJsExecutor(); verify(ctx).getAlarmService(); verify(ctx, times(3)).getDbCallbackExecutor(); verify(ctx).logJsEvalRequest(); @@ -172,7 +168,7 @@ public class TbAlarmNodeTest { Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build(); - when(detailsJs.executeJson(msg)).thenReturn(null); + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(clearedAlarm)); doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); @@ -207,8 +203,6 @@ public class TbAlarmNodeTest { .build(); assertEquals(expectedAlarm, actualAlarm); - - verify(executor, times(1)).executeAsync(any(Callable.class)); } @Test @@ -220,7 +214,7 @@ public class TbAlarmNodeTest { long oldEndDate = System.currentTimeMillis(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); - when(detailsJs.executeJson(msg)).thenReturn(null); + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm)); doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm); @@ -256,8 +250,6 @@ public class TbAlarmNodeTest { .build(); assertEquals(expectedAlarm, actualAlarm); - - verify(executor, times(1)).executeAsync(any(Callable.class)); } @Test @@ -269,7 +261,7 @@ public class TbAlarmNodeTest { long oldEndDate = System.currentTimeMillis(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); -// when(detailsJs.executeJson(msg)).thenReturn(null); + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(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)); @@ -320,12 +312,9 @@ public class TbAlarmNodeTest { when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); - when(ctx.getJsExecutor()).thenReturn(executor); when(ctx.getAlarmService()).thenReturn(alarmService); when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); - mockJsExecutor(); - node = new TbCreateAlarmNode(); node.init(ctx, nodeConfiguration); } catch (TbNodeException ex) { @@ -344,12 +333,9 @@ public class TbAlarmNodeTest { when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); - when(ctx.getJsExecutor()).thenReturn(executor); when(ctx.getAlarmService()).thenReturn(alarmService); when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); - mockJsExecutor(); - node = new TbClearAlarmNode(); node.init(ctx, nodeConfiguration); } catch (TbNodeException ex) { @@ -357,18 +343,6 @@ public class TbAlarmNodeTest { } } - private void mockJsExecutor() { - when(ctx.getJsExecutor()).thenReturn(executor); - doAnswer((Answer>) invocationOnMock -> { - try { - Callable task = (Callable) (invocationOnMock.getArguments())[0]; - return Futures.immediateFuture((Boolean) task.call()); - } catch (Throwable th) { - return Futures.immediateFailedFuture(th); - } - }).when(executor).executeAsync(any(Callable.class)); - } - private void verifyError(TbMsg msg, String message, Class expectedClass) { ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); verify(ctx).tellFailure(same(msg), captor.capture()); From 5bff61c2ca4ad1436e8b8324b85a382e3541d9b1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 11 Dec 2019 15:08:47 +0200 Subject: [PATCH 128/261] Improve JS Executor: add max active scripts parameter --- .../api/jsInvokeMessageProcessor.js | 23 +++++++++++++++++-- .../config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 1 + 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index 6afc02e89c..ac14bcbd57 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -27,11 +27,13 @@ const config = require('config'), const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency')); const useSandbox = config.get('script.use_sandbox') === 'true'; +const maxActiveScripts = Number(config.get('script.max_active_scripts')); function JsInvokeMessageProcessor(producer) { this.producer = producer; this.executor = new JsExecutor(useSandbox); this.scriptMap = {}; + this.scriptIds = []; this.executedScriptsCounter = 0; } @@ -70,7 +72,7 @@ JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, r this.executor.compileScript(compileRequest.scriptBody).then( (script) => { - this.scriptMap[scriptId] = script; + this.cacheScript(scriptId, script); var compileResponse = createCompileResponse(scriptId, true); logger.debug('[%s] Sending success compile response, scriptId: [%s]', requestId, scriptId); this.sendResponse(requestId, responseTopic, scriptId, compileResponse); @@ -126,6 +128,10 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, r var scriptId = getScriptId(releaseRequest); logger.debug('[%s] Processing release request, scriptId: [%s]', requestId, scriptId); if (this.scriptMap[scriptId]) { + var index = this.scriptIds.indexOf(scriptId); + if (index > -1) { + this.scriptIds.splice(index, 1); + } delete this.scriptMap[scriptId]; } var releaseResponse = createReleaseResponse(scriptId, true); @@ -165,7 +171,7 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip } else { self.executor.compileScript(scriptBody).then( (script) => { - self.scriptMap[scriptId] = script; + self.cacheScript(scriptId, script); resolve(script); }, (err) => { @@ -176,6 +182,19 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip }); } +JsInvokeMessageProcessor.prototype.cacheScript = function(scriptId, script) { + if (!this.scriptMap[scriptId]) { + this.scriptIds.push(scriptId); + while (this.scriptIds.length > maxActiveScripts) { + logger.info('Active scripts count [%s] exceeds maximum limit [%s]', this.scriptIds.length, maxActiveScripts); + const prevScriptId = this.scriptIds.shift(); + logger.info('Removing active script with id [%s]', prevScriptId); + delete this.scriptMap[prevScriptId]; + } + } + this.scriptMap[scriptId] = script; +} + function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) { const requestIdBits = Utils.UUIDToBits(requestId); return { diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index c3caf15a9a..22b1082f72 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -27,3 +27,4 @@ logger: script: use_sandbox: "SCRIPT_USE_SANDBOX" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" + max_active_scripts: "MAX_ACTIVE_SCRIPTS" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 9688722109..95a847a5bb 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -28,3 +28,4 @@ logger: script: use_sandbox: "true" script_body_trace_frequency: "1000" + max_active_scripts: "1000" From bb05ce046bf98d74a2cf8e5b29104ef62ee53f4e Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 12 Dec 2019 08:26:19 +0200 Subject: [PATCH 129/261] Suppress spam log message --- .../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 c3c8dc7803..ac0644719d 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 @@ -289,7 +289,7 @@ 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()); + log.debug("Calculating state updates for {} devices", deviceStates.size()); for (DeviceId deviceId : deviceIds) { DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); if (stateData != null) { From a2cc974865ef16748918a98f8240176d401dddf3 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 13 Dec 2019 15:53:19 +0200 Subject: [PATCH 130/261] Critical Bug Fix for SQL inserts --- .../AttributeKvInsertRepository.java | 62 ++++++++++--------- .../timescale/TimescaleInsertRepository.java | 33 +++++----- .../sqlts/ts/PsqlLatestInsertRepository.java | 58 ++++++++--------- .../ts/PsqlTimeseriesInsertRepository.java | 33 +++++----- 4 files changed, 96 insertions(+), 90 deletions(-) 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 0a537cbe01..ce7c96d919 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 @@ -143,31 +143,32 @@ 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, replaceNullChars(entities.get(i).getStrValue())); + AttributeKvEntity kvEntity = entities.get(i); + ps.setString(1, replaceNullChars(kvEntity.getStrValue())); - if (entities.get(i).getLongValue() != null) { - ps.setLong(2, entities.get(i).getLongValue()); + if (kvEntity.getLongValue() != null) { + ps.setLong(2, kvEntity.getLongValue()); } else { ps.setNull(2, Types.BIGINT); } - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(3, entities.get(i).getDoubleValue()); + if (kvEntity.getDoubleValue() != null) { + ps.setDouble(3, kvEntity.getDoubleValue()); } else { ps.setNull(3, Types.DOUBLE); } - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(4, entities.get(i).getBooleanValue()); + if (kvEntity.getBooleanValue() != null) { + ps.setBoolean(4, kvEntity.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()); + ps.setLong(5, kvEntity.getLastUpdateTs()); + ps.setString(6, kvEntity.getId().getEntityType().name()); + ps.setString(7, kvEntity.getId().getEntityId()); + ps.setString(8, kvEntity.getId().getAttributeType()); + ps.setString(9, kvEntity.getId().getAttributeKey()); } @Override @@ -193,39 +194,40 @@ public abstract class AttributeKvInsertRepository { 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, 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()); - ps.setLong(11, insertEntities.get(i).getLongValue()); + AttributeKvEntity kvEntity = insertEntities.get(i); + ps.setString(1, kvEntity.getId().getEntityType().name()); + ps.setString(2, kvEntity.getId().getEntityId()); + ps.setString(3, kvEntity.getId().getAttributeType()); + ps.setString(4, kvEntity.getId().getAttributeKey()); + ps.setString(5, replaceNullChars(kvEntity.getStrValue())); + ps.setString(10, replaceNullChars(kvEntity.getStrValue())); + + if (kvEntity.getLongValue() != null) { + ps.setLong(6, kvEntity.getLongValue()); + ps.setLong(11, kvEntity.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()); + if (kvEntity.getDoubleValue() != null) { + ps.setDouble(7, kvEntity.getDoubleValue()); + ps.setDouble(12, kvEntity.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()); + if (kvEntity.getBooleanValue() != null) { + ps.setBoolean(8, kvEntity.getBooleanValue()); + ps.setBoolean(13, kvEntity.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()); + ps.setLong(9, kvEntity.getLastUpdateTs()); + ps.setLong(14, kvEntity.getLastUpdateTs()); } @Override 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 b47c79cb98..a79ea892ed 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 @@ -57,34 +57,35 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor 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()); + TimescaleTsKvEntity tsKvEntity = entities.get(i); + ps.setString(1, tsKvEntity.getTenantId()); + ps.setString(2, tsKvEntity.getEntityId()); + ps.setString(3, tsKvEntity.getKey()); + ps.setLong(4, tsKvEntity.getTs()); + + if (tsKvEntity.getBooleanValue() != null) { + ps.setBoolean(5, tsKvEntity.getBooleanValue()); + ps.setBoolean(9, tsKvEntity.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())); + ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); - if (entities.get(i).getLongValue() != null) { - ps.setLong(7, entities.get(i).getLongValue()); - ps.setLong(11, entities.get(i).getLongValue()); + if (tsKvEntity.getLongValue() != null) { + ps.setLong(7, tsKvEntity.getLongValue()); + ps.setLong(11, tsKvEntity.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()); + if (tsKvEntity.getDoubleValue() != null) { + ps.setDouble(8, tsKvEntity.getDoubleValue()); + ps.setDouble(12, tsKvEntity.getDoubleValue()); } else { ps.setNull(8, Types.DOUBLE); ps.setNull(12, Types.DOUBLE); 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 92252e9d18..e0dad44f3b 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 @@ -65,31 +65,32 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { 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()); + TsKvLatestEntity tsKvLatestEntity = entities.get(i); + ps.setLong(1, tsKvLatestEntity.getTs()); - if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(2, entities.get(i).getBooleanValue()); + if (tsKvLatestEntity.getBooleanValue() != null) { + ps.setBoolean(2, tsKvLatestEntity.getBooleanValue()); } else { ps.setNull(2, Types.BOOLEAN); } - ps.setString(3, replaceNullChars(entities.get(i).getStrValue())); + ps.setString(3, replaceNullChars(tsKvLatestEntity.getStrValue())); - if (entities.get(i).getLongValue() != null) { - ps.setLong(4, entities.get(i).getLongValue()); + if (tsKvLatestEntity.getLongValue() != null) { + ps.setLong(4, tsKvLatestEntity.getLongValue()); } else { ps.setNull(4, Types.BIGINT); } - if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(5, entities.get(i).getDoubleValue()); + if (tsKvLatestEntity.getDoubleValue() != null) { + ps.setDouble(5, tsKvLatestEntity.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()); + ps.setString(6, tsKvLatestEntity.getEntityType().name()); + ps.setString(7, tsKvLatestEntity.getEntityId()); + ps.setString(8, tsKvLatestEntity.getKey()); } @Override @@ -115,35 +116,36 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { 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()); + TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i); + ps.setString(1, tsKvLatestEntity.getEntityType().name()); + ps.setString(2, tsKvLatestEntity.getEntityId()); + ps.setString(3, tsKvLatestEntity.getKey()); + ps.setLong(4, tsKvLatestEntity.getTs()); + ps.setLong(9, tsKvLatestEntity.getTs()); + + if (tsKvLatestEntity.getBooleanValue() != null) { + ps.setBoolean(5, tsKvLatestEntity.getBooleanValue()); + ps.setBoolean(10, tsKvLatestEntity.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())); + ps.setString(6, replaceNullChars(tsKvLatestEntity.getStrValue())); + ps.setString(11, replaceNullChars(tsKvLatestEntity.getStrValue())); - if (insertEntities.get(i).getLongValue() != null) { - ps.setLong(7, insertEntities.get(i).getLongValue()); - ps.setLong(12, insertEntities.get(i).getLongValue()); + if (tsKvLatestEntity.getLongValue() != null) { + ps.setLong(7, tsKvLatestEntity.getLongValue()); + ps.setLong(12, tsKvLatestEntity.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()); + if (tsKvLatestEntity.getDoubleValue() != null) { + ps.setDouble(8, tsKvLatestEntity.getDoubleValue()); + ps.setDouble(13, tsKvLatestEntity.getDoubleValue()); } else { ps.setNull(8, Types.DOUBLE); ps.setNull(13, Types.DOUBLE); 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 edc37822b1..e1c8eb7ee2 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 @@ -99,34 +99,35 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo 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()); + TsKvEntity tsKvEntity = entities.get(i); + ps.setString(1, tsKvEntity.getEntityType().name()); + ps.setString(2, tsKvEntity.getEntityId()); + ps.setString(3, tsKvEntity.getKey()); + ps.setLong(4, tsKvEntity.getTs()); + + if (tsKvEntity.getBooleanValue() != null) { + ps.setBoolean(5, tsKvEntity.getBooleanValue()); + ps.setBoolean(9, tsKvEntity.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())); + ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); - if (entities.get(i).getLongValue() != null) { - ps.setLong(7, entities.get(i).getLongValue()); - ps.setLong(11, entities.get(i).getLongValue()); + if (tsKvEntity.getLongValue() != null) { + ps.setLong(7, tsKvEntity.getLongValue()); + ps.setLong(11, tsKvEntity.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()); + if (tsKvEntity.getDoubleValue() != null) { + ps.setDouble(8, tsKvEntity.getDoubleValue()); + ps.setDouble(12, tsKvEntity.getDoubleValue()); } else { ps.setNull(8, Types.DOUBLE); ps.setNull(12, Types.DOUBLE); From f23cfc98807677e8edd4247950ddd63c4cabb104 Mon Sep 17 00:00:00 2001 From: VoBa Date: Mon, 16 Dec 2019 16:06:51 +0200 Subject: [PATCH 131/261] Added device creation with token (#2272) * Added device creation with token * Fixes after code review * Added device creation with token --- .../server/controller/DeviceController.java | 5 ++-- .../server/dao/device/DeviceService.java | 2 ++ .../server/dao/device/DeviceServiceImpl.java | 12 +++++++- .../thingsboard/client/tools/RestClient.java | 29 ++++++++++++++----- 4 files changed, 37 insertions(+), 11 deletions(-) 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 698dcfb3f3..7ab4ab07a9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -82,7 +82,8 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody - public Device saveDevice(@RequestBody Device device) throws ThingsboardException { + public Device saveDevice(@RequestBody Device device, + @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { try { device.setTenantId(getCurrentUser().getTenantId()); @@ -91,7 +92,7 @@ public class DeviceController extends BaseController { accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, device.getId(), device); - Device savedDevice = checkNotNull(deviceService.saveDevice(device)); + Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); actorService .onDeviceNameOrTypeUpdate( diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 793bb9829b..b9940d73e3 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -37,6 +37,8 @@ public interface DeviceService { Device saveDevice(Device device); + Device saveDeviceWithAccessToken(Device device, String accessToken); + Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId); Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId); 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 a26e275a96..0618d3fcf8 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 @@ -119,9 +119,19 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceOpt.orElse(null); } + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Override + public Device saveDeviceWithAccessToken(Device device, String accessToken) { + return doSaveDevice(device, accessToken); + } + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") @Override public Device saveDevice(Device device) { + return doSaveDevice(device, null); + } + + private Device doSaveDevice(Device device, String accessToken) { log.trace("Executing saveDevice [{}]", device); deviceValidator.validate(device, Device::getTenantId); Device savedDevice; @@ -143,7 +153,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(20)); + deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20)); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); } return savedDevice; 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 97ce718cc8..180e7f6f57 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -225,13 +225,6 @@ public class RestClient implements ClientHttpRequestInterceptor { return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); } - public Device createDevice(String name, String type) { - Device device = new Device(); - device.setName(name); - device.setType(type); - return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); - } - public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) { DeviceCredentials deviceCredentials = getCredentials(deviceId); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); @@ -239,10 +232,30 @@ public class RestClient implements ClientHttpRequestInterceptor { return saveDeviceCredentials(deviceCredentials); } + public Device createDevice(String name, String type) { + Device device = new Device(); + device.setName(name); + device.setType(type); + return doCreateDevice(device, null); + } + public Device createDevice(Device device) { - return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); + return doCreateDevice(device, null); } + public Device createDevice(Device device, String accessToken) { + return doCreateDevice(device, accessToken); + } + + private Device doCreateDevice(Device device, String accessToken) { + Map params = new HashMap<>(); + String deviceCreationUrl = "/api/device"; + if (!StringUtils.isEmpty(accessToken)) { + deviceCreationUrl = deviceCreationUrl + "?accessToken={accessToken}"; + params.put("accessToken", accessToken); + } + return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody(); + } public Asset createAsset(Asset asset) { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } From cfe7e4260274d90ad992214e4cdba6d7733c9200 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 18 Dec 2019 18:24:28 +0200 Subject: [PATCH 132/261] JS Stats for Nashorn JS Executor --- .../AbstractNashornJsInvokeService.java | 107 ++++++++++++++---- .../server/service/script/JsStatCallback.java | 31 +++++ .../src/main/resources/thingsboard.yml | 5 + 3 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 62e7c24b1a..807c43ffa7 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -15,21 +15,33 @@ */ 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 delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandboxes; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptException; import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; @Slf4j public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { @@ -37,9 +49,46 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer private NashornSandbox sandbox; private ScriptEngine engine; private ExecutorService monitorExecutorService; + private ScheduledExecutorService timeoutExecutorService; + + private final AtomicInteger jsPushedMsgs = new AtomicInteger(0); + private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0); + private final AtomicInteger jsEvalMsgs = new AtomicInteger(0); + private final AtomicInteger jsFailedMsgs = new AtomicInteger(0); + private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0); + private final FutureCallback evalCallback = new JsStatCallback(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); + private final FutureCallback invokeCallback = new JsStatCallback(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); + + @Autowired + @Getter + private JsExecutorService jsExecutor; + + @Value("${js.local.max_requests_timeout:0}") + private long maxRequestsTimeout; + + @Value("${js.local.stats.enabled:false}") + private boolean statsEnabled; + + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}") + public void printStats() { + if (statsEnabled) { + int pushedMsgs = jsPushedMsgs.getAndSet(0); + int invokeMsgs = jsInvokeMsgs.getAndSet(0); + int evalMsgs = jsEvalMsgs.getAndSet(0); + int failed = jsFailedMsgs.getAndSet(0); + int timedOut = jsTimeoutMsgs.getAndSet(0); + if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { + log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", + pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); + } + } + } @PostConstruct public void init() { + if (maxRequestsTimeout > 0) { + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(); + } if (useJsSandbox()) { sandbox = NashornSandboxes.create(); monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize()); @@ -59,6 +108,9 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer if (monitorExecutorService != null) { monitorExecutorService.shutdownNow(); } + if (timeoutExecutorService != null) { + timeoutExecutorService.shutdownNow(); + } } protected abstract boolean useJsSandbox(); @@ -69,34 +121,49 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Override protected ListenableFuture doEval(UUID scriptId, String functionName, String jsScript) { - try { - if (useJsSandbox()) { - sandbox.eval(jsScript); - } else { - engine.eval(jsScript); + jsPushedMsgs.incrementAndGet(); + ListenableFuture result = jsExecutor.executeAsync(() -> { + try { + if (useJsSandbox()) { + sandbox.eval(jsScript); + } else { + engine.eval(jsScript); + } + scriptIdToNameMap.put(scriptId, functionName); + return scriptId; + } catch (Exception e) { + log.warn("Failed to compile JS script: {}", e.getMessage(), e); + throw new ExecutionException(e); } - scriptIdToNameMap.put(scriptId, functionName); - } catch (Exception e) { - log.warn("Failed to compile JS script: {}", e.getMessage(), e); - return Futures.immediateFailedFuture(e); + }); + if (maxRequestsTimeout > 0) { + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); } - return Futures.immediateFuture(scriptId); + Futures.addCallback(result, evalCallback); + return result; } @Override protected ListenableFuture doInvokeFunction(UUID scriptId, String functionName, Object[] args) { - try { - Object result; - if (useJsSandbox()) { - result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); - } else { - result = ((Invocable) engine).invokeFunction(functionName, args); + jsPushedMsgs.incrementAndGet(); + ListenableFuture result = jsExecutor.executeAsync(() -> { + try { + if (useJsSandbox()) { + return sandbox.getSandboxedInvocable().invokeFunction(functionName, args); + } else { + return ((Invocable) engine).invokeFunction(functionName, args); + } + } catch (Exception e) { + onScriptExecutionError(scriptId); + throw new ExecutionException(e); } - return Futures.immediateFuture(result); - } catch (Exception e) { - onScriptExecutionError(scriptId); - return Futures.immediateFailedFuture(e); + }); + + if (maxRequestsTimeout > 0) { + result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); } + Futures.addCallback(result, invokeCallback); + return result; } protected void doRelease(UUID scriptId, String functionName) throws ScriptException { diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java b/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java new file mode 100644 index 0000000000..cceba04f4f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java @@ -0,0 +1,31 @@ +package org.thingsboard.server.service.script; + +import com.google.common.util.concurrent.FutureCallback; +import lombok.AllArgsConstructor; + +import javax.annotation.Nullable; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +@AllArgsConstructor +public class JsStatCallback implements FutureCallback { + + private final AtomicInteger jsSuccessMsgs; + private final AtomicInteger jsTimeoutMsgs; + private final AtomicInteger jsFailedMsgs; + + + @Override + public void onSuccess(@Nullable T result) { + jsSuccessMsgs.incrementAndGet(); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { + jsTimeoutMsgs.incrementAndGet(); + } else { + jsFailedMsgs.incrementAndGet(); + } + } +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index fcf62a659b..c03240e17f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -464,6 +464,11 @@ js: 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}" + # JS Eval max request timeout. 0 - no timeout + max_requests_timeout: "${LOCAL_JS_MAX_REQUEST_TIMEOUT:0}" + stats: + enabled: "${TB_JS_LOCAL_STATS_ENABLED:false}" + print_interval_ms: "${TB_JS_LOCAL_STATS_PRINT_INTERVAL_MS:10000}" # Remote JavaScript environment properties remote: # JS Eval request topic From 91b07f6c9000a9d9a9c1a3b68bfe6e521ed2a0b4 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 19 Dec 2019 16:15:27 +0200 Subject: [PATCH 133/261] Download Dependencies maven profile --- pom.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pom.xml b/pom.xml index 898c417300..0cbbd05bea 100755 --- a/pom.xml +++ b/pom.xml @@ -115,6 +115,15 @@ true + + + + download-dependencies + + true + true + + From 43503f9c61a9ad4389359c868f4d9a66a2097e3f Mon Sep 17 00:00:00 2001 From: vlad Date: Thu, 19 Dec 2019 18:00:24 +0200 Subject: [PATCH 134/261] Activity event support for Copy to view node added --- .../server/service/state/DefaultDeviceStateService.java | 6 ++---- .../engine/action/TbCopyAttributesToEntityViewNode.java | 4 +++- 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 ac0644719d..eb0f7d09c0 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 @@ -70,10 +70,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; -import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; -import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.DataConstants.*; /** * Created by ashvayka on 01.05.18. @@ -334,6 +331,7 @@ public class DefaultDeviceStateService implements DeviceStateService { DeviceState state = stateData.getState(); long ts = System.currentTimeMillis(); stateData.getState().setLastActivityTime(ts); + stateData.getMetaData().putValue("scope", SERVER_SCOPE); pushRuleEngineMessage(stateData, ACTIVITY_EVENT); save(deviceId, LAST_ACTIVITY_TIME, ts); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 603363b350..bcc89a0e4e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -71,6 +71,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || DataConstants.ATTRIBUTES_DELETED.equals(msg.getType()) || + DataConstants.ACTIVITY_EVENT.equals(msg.getType()) || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); @@ -87,7 +88,8 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { long endTime = entityView.getEndTimeMs(); if ((endTime != 0 && endTime > now && startTime < now) || (endTime == 0 && startTime < now)) { if (DataConstants.ATTRIBUTES_UPDATED.equals(msg.getType()) || - SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType())) { + DataConstants.ACTIVITY_EVENT.equals(msg.getType()) || + SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msg.getType()) ) { Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); List filteredAttributes = attributes.stream().filter(attr -> attributeContainsInEntityView(scope, attr.getKey(), entityView)).collect(Collectors.toList()); From 27d0fc3b0a21724755ba1a3df3520b62fc8676f9 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 20 Dec 2019 12:12:33 +0200 Subject: [PATCH 135/261] Missing License Header --- .../server/service/script/JsStatCallback.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java b/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java index cceba04f4f..a1cf38b434 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java @@ -1,3 +1,18 @@ +/** + * 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.script; import com.google.common.util.concurrent.FutureCallback; From 0ea991019a1a5fd8b61ff096159226417dda3d6f Mon Sep 17 00:00:00 2001 From: vlad Date: Fri, 20 Dec 2019 12:32:12 +0200 Subject: [PATCH 136/261] Activity added --- 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 fcf62a659b..2173c39670 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -384,7 +384,7 @@ spring: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" - password: "${SPRING_DATASOURCE_PASSWORD:postgres}" + password: "${SPRING_DATASOURCE_PASSWORD:123456}" hikari: maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:50}" From cacdf09eef1a1f68588e5a5d4349c0458c9043a6 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 20 Dec 2019 13:50:07 +0200 Subject: [PATCH 137/261] More clear thread names --- .../actors/service/DefaultActorService.java | 2 - .../server/controller/RpcController.java | 15 +----- .../controller/TelemetryController.java | 3 +- .../cluster/discovery/ZkDiscoveryService.java | 3 +- .../service/rpc/DefaultDeviceRpcService.java | 3 +- .../AbstractNashornJsInvokeService.java | 3 +- .../service/security/AccessValidator.java | 3 +- .../state/DefaultDeviceStateService.java | 3 +- .../DefaultTelemetrySubscriptionService.java | 5 +- .../BaseRuleChainTransactionService.java | 3 +- .../RemoteRuleEngineTransportService.java | 3 +- .../service/update/DefaultUpdateService.java | 3 +- .../src/main/resources/thingsboard.yml | 4 -- common/transport/transport-api/pom.xml | 4 ++ .../service/AbstractTransportService.java | 3 +- .../service/RemoteTransportService.java | 3 +- .../common/util/ThingsBoardThreadFactory.java | 54 +++++++++++++++++++ .../server/dao/alarm/BaseAlarmService.java | 3 +- .../dao/audit/CassandraAuditLogDao.java | 3 +- .../dao/nosql/CassandraAbstractAsyncDao.java | 3 +- .../sql/ScheduledLogExecutorComponent.java | 3 +- .../server/dao/sql/TbSqlBlockingQueue.java | 3 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 38 ------------- .../timescale/TimescaleTimeseriesDao.java | 4 +- .../server/dao/sqlts/ts/JpaTimeseriesDao.java | 2 +- .../util/AbstractBufferedRateExecutor.java | 5 +- 26 files changed, 100 insertions(+), 81 deletions(-) create mode 100644 common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java 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 852fbe2884..2979d9310b 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 @@ -95,8 +95,6 @@ public class DefaultActorService implements ActorService { private ActorRef rpcManagerActor; - private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); - @PostConstruct public void initActorSystem() { log.info("Initializing Actor system."); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java index 3b624e9b94..0980ca068c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -76,20 +77,6 @@ public class RpcController extends BaseController { @Autowired private AccessValidator accessValidator; - private ExecutorService executor; - - @PostConstruct - public void initExecutor() { - executor = Executors.newSingleThreadExecutor(); - } - - @PreDestroy - public void shutdownExecutor() { - if (executor != null) { - executor.shutdownNow(); - } - } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index d46e6475c0..a7987cec3d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -37,6 +37,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; @@ -108,7 +109,7 @@ public class TelemetryController extends BaseController { @PostConstruct public void initExecutor() { - executor = Executors.newSingleThreadExecutor(); + executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller")); } @PreDestroy diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java index e44c28dc39..a34c8dded0 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -41,6 +41,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; @@ -114,7 +115,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); - reconnectExecutorService = Executors.newSingleThreadExecutor(); + reconnectExecutorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("zk-discovery")); log.info("Initializing discovery service using ZK connect string: {}", zkUrl); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java index 2e1078fa8e..5fe8cb30df 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.actors.service.ActorService; @@ -83,7 +84,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService { @PostConstruct public void initExecutor() { - rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(); + rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("rpc-callback")); } @PreDestroy diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 807c43ffa7..6c0afbf2e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -87,7 +88,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @PostConstruct public void init() { if (maxRequestsTimeout > 0) { - timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(); + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); } if (useJsSandbox()) { sandbox = NashornSandboxes.create(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 6b6e2553fa..f49db67e96 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -24,6 +24,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityView; @@ -105,7 +106,7 @@ public class AccessValidator { @PostConstruct public void initExecutor() { - executor = Executors.newSingleThreadExecutor(); + executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("access-validator")); } @PreDestroy 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 ac0644719d..f162d05419 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 @@ -31,6 +31,7 @@ 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.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -144,7 +145,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @PostConstruct public void init() { // Should be always single threaded due to absence of locks. - queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor()); + queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state"))); queueExecutor.submit(this::initStateFromDB); queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); //TODO: schedule persistence in v2.1; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index c4ddcf654b..a829fa371b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.actors.service.ActorService; @@ -110,8 +111,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @PostConstruct public void initExecutor() { - tsCallBackExecutor = Executors.newSingleThreadExecutor(); - wsCallBackExecutor = Executors.newSingleThreadExecutor(); + tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ts-sub-callback")); + wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback")); } @PreDestroy 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 b40e2b93fd..080478c4c4 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 @@ -19,6 +19,7 @@ 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.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RuleChainTransactionService; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.TbMsg; @@ -71,7 +72,7 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ @PostConstruct public void init() { - timeoutExecutor = Executors.newSingleThreadExecutor(); + timeoutExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("rule-chain-transaction")); executeOnTimeout(); } 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 1c1c7be483..d2c18b2406 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 @@ -32,6 +32,7 @@ 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.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; @@ -103,7 +104,7 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ private TBKafkaConsumerTemplate ruleEngineConsumer; private TBKafkaProducerTemplate notificationsProducer; - private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); + private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-main-consumer")); private volatile boolean stopped = false; 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 14cbf95861..479f5afcb9 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,6 +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.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.UpdateMessage; import javax.annotation.PostConstruct; @@ -50,7 +51,7 @@ public class DefaultUpdateService implements UpdateService { @Value("${updates.enabled}") private boolean updatesEnabled; - private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service")); private ScheduledFuture checkUpdatesFuture = null; private RestTemplate restClient = new RestTemplate(); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c03240e17f..b3662a60e1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -195,10 +195,6 @@ cassandra: # SQL configuration parameters sql: - # Specify executor service type used to perform timeseries insert tasks: SINGLE or FIXED - 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}" diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index dc6cbfd1ba..25a9b456fb 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -44,6 +44,10 @@ org.thingsboard.common message + + org.thingsboard.common + util + org.thingsboard.common queue diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java index b09e94202d..bcafa177a4 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport.service; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -300,7 +301,7 @@ public abstract class AbstractTransportService implements TransportService { new TbRateLimits(perTenantLimitsConf); new TbRateLimits(perDevicesLimitsConf); } - this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(); + this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler")); this.transportCallbackExecutor = Executors.newWorkStealingPool(20); this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); } 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 632ce16bc1..5397837b68 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 @@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; @@ -100,7 +101,7 @@ public class RemoteTransportService extends AbstractTransportService { private TBKafkaProducerTemplate ruleEngineProducer; private TBKafkaConsumerTemplate mainConsumer; - private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); + private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("remote-transport-consumer")); private volatile boolean stopped = false; diff --git a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java new file mode 100644 index 0000000000..ff08349bd4 --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.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.common.util; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Copy of Executors.DefaultThreadFactory but with ability to set name of the pool + */ +public class ThingsBoardThreadFactory implements ThreadFactory { + private static final AtomicInteger poolNumber = new AtomicInteger(1); + private final ThreadGroup group; + private final AtomicInteger threadNumber = new AtomicInteger(1); + private final String namePrefix; + + public static ThingsBoardThreadFactory forName(String name) { + return new ThingsBoardThreadFactory(name); + } + + private ThingsBoardThreadFactory(String name) { + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + namePrefix = name + "-" + + poolNumber.getAndIncrement() + + "-thread-"; + } + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, + namePrefix + threadNumber.getAndIncrement(), + 0); + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } +} 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 8a4279fe7f..d46445778d 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 @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.alarm.Alarm; @@ -81,7 +82,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @PostConstruct public void startExecutor() { - readResultsProcessingExecutor = Executors.newCachedThreadPool(); + readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("alarm-service")); } @PreDestroy diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java index 675a24a7f7..d75014a389 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java @@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; @@ -117,7 +118,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao implements TbSqlQueue { @Override public void init(ScheduledLogExecutorComponent logExecutor, Consumer> saveFunction) { this.logExecutor = logExecutor; - executor = Executors.newSingleThreadExecutor(); + executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("sql-queue-" + params.getLogName().toLowerCase())); executor.submit(() -> { String logName = params.getLogName(); int batchSize = params.getBatchSize(); 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 a7efad1e88..15a012df66 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 @@ -44,44 +44,6 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx private static final String DESC_ORDER = "DESC"; - @Value("${sql.ts_inserts_executor_type}") - private String insertExecutorType; - - @Value("${sql.ts_inserts_fixed_thread_pool_size}") - private int insertFixedThreadPoolSize; - - @Value("${spring.datasource.hikari.maximumPoolSize}") - private int maximumPoolSize; - - protected ListeningExecutorService insertService; - - @PostConstruct - void init() { - Optional executorTypeOptional = TsInsertExecutorType.parse(insertExecutorType); - TsInsertExecutorType executorType; - executorType = executorTypeOptional.orElse(TsInsertExecutorType.FIXED); - switch (executorType) { - case SINGLE: - insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); - break; - case FIXED: - case CACHED: - int poolSize = insertFixedThreadPoolSize; - if (poolSize <= 0) { - poolSize = maximumPoolSize * 4; - } - insertService = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool(poolSize)); - break; - } - } - - @PreDestroy - void preDestroy() { - if (insertService != null) { - insertService.shutdown(); - } - } - protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() 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 961f545567..cbd2d2cdc8 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 @@ -170,12 +170,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Override public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return insertService.submit(() -> null); + return Futures.immediateFuture(null); } @Override public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - return insertService.submit(() -> null); + return Futures.immediateFuture(null); } @Override 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 7c198d9a73..71b156b81f 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 @@ -334,7 +334,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese @Override public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return insertService.submit(() -> null); + return Futures.immediateFuture(null); } @Override 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 a553aa5f9c..54fdfbf2d5 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 @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.dao.nosql.CassandraStatementTask; @@ -67,9 +68,9 @@ public abstract class AbstractBufferedRateExecutor(queueLimit); - this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads); + this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-dispatcher")); this.callbackExecutor = Executors.newWorkStealingPool(callbackThreads); - this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(); + this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout")); this.perTenantLimitsEnabled = perTenantLimitsEnabled; this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; for (int i = 0; i < dispatcherThreads; i++) { From 95752ff3b9d70da946c1fb6a6f714943b6fd73a2 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 20 Dec 2019 15:58:55 +0200 Subject: [PATCH 138/261] Performance Improvement for Device State Service --- .../state/DefaultDeviceStateService.java | 34 +++++++++++++------ 1 file changed, 23 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 f162d05419..0a6ce7f158 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 @@ -141,6 +141,8 @@ public class DefaultDeviceStateService implements DeviceStateService { private ListeningScheduledExecutorService queueExecutor; private ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); private ConcurrentMap deviceStates = new ConcurrentHashMap<>(); + private ConcurrentMap deviceLastReportedActivity = new ConcurrentHashMap<>(); + private ConcurrentMap deviceLastSavedActivity = new ConcurrentHashMap<>(); @PostConstruct public void init() { @@ -175,6 +177,7 @@ public class DefaultDeviceStateService implements DeviceStateService { @Override public void onDeviceActivity(DeviceId deviceId) { + deviceLastReportedActivity.put(deviceId, System.currentTimeMillis()); queueExecutor.submit(() -> onDeviceActivitySync(deviceId)); } @@ -245,6 +248,8 @@ public class DefaultDeviceStateService implements DeviceStateService { tenantDeviceSet.remove(device.getId()); } deviceStates.remove(device.getId()); + deviceLastReportedActivity.remove(device.getId()); + deviceLastSavedActivity.remove(device.getId()); } } try { @@ -305,6 +310,8 @@ public class DefaultDeviceStateService implements DeviceStateService { } else { log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); deviceStates.remove(deviceId); + deviceLastReportedActivity.remove(deviceId); + deviceLastSavedActivity.remove(deviceId); } } } @@ -330,17 +337,20 @@ public class DefaultDeviceStateService implements DeviceStateService { } private void onDeviceActivitySync(DeviceId deviceId) { - DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); - if (stateData != null) { - DeviceState state = stateData.getState(); - long ts = System.currentTimeMillis(); - stateData.getState().setLastActivityTime(ts); - pushRuleEngineMessage(stateData, ACTIVITY_EVENT); - save(deviceId, LAST_ACTIVITY_TIME, ts); - - if (!state.isActive()) { - state.setActive(true); - save(deviceId, ACTIVITY_STATE, state.isActive()); + long lastReportedActivity = deviceLastReportedActivity.getOrDefault(deviceId, 0L); + long lastSavedActivity = deviceLastSavedActivity.getOrDefault(deviceId, 0L); + if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) { + DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); + if (stateData != null) { + DeviceState state = stateData.getState(); + stateData.getState().setLastActivityTime(lastReportedActivity); + pushRuleEngineMessage(stateData, ACTIVITY_EVENT); + save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); + deviceLastSavedActivity.put(deviceId, lastReportedActivity); + if (!state.isActive()) { + state.setActive(true); + save(deviceId, ACTIVITY_STATE, state.isActive()); + } } } } @@ -431,6 +441,8 @@ public class DefaultDeviceStateService implements DeviceStateService { Optional address = routingService.resolveById(deviceId); if (!address.isPresent()) { deviceStates.remove(deviceId); + deviceLastReportedActivity.remove(deviceId); + deviceLastSavedActivity.remove(deviceId); Set deviceIds = tenantDevices.get(tenantId); if (deviceIds != null) { deviceIds.remove(deviceId); From d55c374e364d3d596e9dfd106fb65e92bf3be39c Mon Sep 17 00:00:00 2001 From: vlad Date: Fri, 20 Dec 2019 16:16:01 +0200 Subject: [PATCH 139/261] reverted password --- 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 eeeff502ae..c03240e17f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -384,7 +384,7 @@ spring: driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}" url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}" username: "${SPRING_DATASOURCE_USERNAME:postgres}" - password: "${SPRING_DATASOURCE_PASSWORD:123456}" + password: "${SPRING_DATASOURCE_PASSWORD:postgres}" hikari: maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:50}" From 29a29662e6fc716c1a6742114dbd2bfbaedd0cb7 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Mon, 9 Dec 2019 16:27:14 +0200 Subject: [PATCH 140/261] rule-node-ui-changes --- .../public/static/rulenode/rulenode-core-config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 2ed9e929b1..54159b3feb 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.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}])); +!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 }}
tb.rulenode.relation-types-list-hint
"},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,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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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(_),P=n(71),F=i(P),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",F.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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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 1b4ef0c9fa221392aebad4ada776512e2a79492f Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 20 Dec 2019 17:42:07 +0200 Subject: [PATCH 141/261] Latest 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 54159b3feb..0358f23448 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 }}
tb.rulenode.relation-types-list-hint
"},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,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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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(_),P=n(71),F=i(P),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",F.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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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.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.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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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 89dbad2cf70014dfe45fbb846418019bffbd8137 Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Mon, 23 Dec 2019 14:06:29 +0100 Subject: [PATCH 142/261] added delete alarm to ui service (#2266) --- ui/src/app/api/alarm.service.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/ui/src/app/api/alarm.service.js b/ui/src/app/api/alarm.service.js index f1b0513ec6..83848e2817 100644 --- a/ui/src/app/api/alarm.service.js +++ b/ui/src/app/api/alarm.service.js @@ -47,6 +47,7 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { saveAlarm: saveAlarm, ackAlarm: ackAlarm, clearAlarm: clearAlarm, + deleteAlarm: deleteAlarm, getAlarms: getAlarms, getHighestAlarmSeverity: getHighestAlarmSeverity, pollAlarms: pollAlarms, @@ -132,6 +133,21 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) { return deferred.promise; } + function deleteAlarm(alarmId, ignoreErrors, config) { + var deferred = $q.defer(); + var url = '/api/alarm/' + alarmId; + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.delete(url, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail() { + deferred.reject(); + }); + return deferred.promise; + } + function getAlarms(entityType, entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, ascOrder, config) { var deferred = $q.defer(); var url = '/api/alarm/' + entityType + '/' + entityId + '?limit=' + pageLink.limit; From cea1381482bead3271998408cc1cb73506061e7b Mon Sep 17 00:00:00 2001 From: Michael Hamburger Date: Mon, 23 Dec 2019 14:07:12 +0100 Subject: [PATCH 143/261] add upgrade_dev_db.sh script, which allows to upgrade an already existing developer database (#2267) --- .../main/scripts/install/upgrade_dev_db.sh | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100755 application/src/main/scripts/install/upgrade_dev_db.sh diff --git a/application/src/main/scripts/install/upgrade_dev_db.sh b/application/src/main/scripts/install/upgrade_dev_db.sh new file mode 100755 index 0000000000..31c68ddf88 --- /dev/null +++ b/application/src/main/scripts/install/upgrade_dev_db.sh @@ -0,0 +1,68 @@ +#!/bin/bash +# +# 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. +# + +for i in "$@" +do +case $i in + --fromVersion=*) + FROM_VERSION="${i#*=}" + shift + ;; + *) + # unknown option + ;; +esac +done + +if [[ -z "${FROM_VERSION// }" ]]; then + echo "--fromVersion parameter is invalid or unspecified!" + echo "Usage: upgrade_dev_db.sh --fromVersion={VERSION}" + exit 1 +else + fromVersion="${FROM_VERSION// }" +fi + +BASE=${project.basedir}/target +CONF_FOLDER=${BASE}/conf +jarfile="${BASE}/thingsboard-${project.version}-boot.jar" +installDir=${BASE}/data +loadDemo=true + + +export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@" +export LOADER_PATH=${BASE}/conf,${BASE}/extensions +export SQL_DATA_FOLDER=${SQL_DATA_FOLDER:-/tmp} + + +run_user="$USER" + +sudo -u "$run_user" -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ + -Dinstall.data_dir=${installDir} \ + -Dinstall.load_demo=${loadDemo} \ + -Dspring.jpa.hibernate.ddl-auto=none \ + -Dinstall.upgrade=true \ + -Dinstall.upgrade.from_version=${fromVersion} \ + -Dlogging.config=logback.xml \ + org.springframework.boot.loader.PropertiesLauncher" + +if [ $? -ne 0 ]; then + echo "ThingsBoard DB installation failed!" +else + echo "ThingsBoard DB installed successfully!" +fi + +exit $? From 4f8616d0af932698a8adf44132387771a05deadf Mon Sep 17 00:00:00 2001 From: Mirco Pizzichini <52463156+mircopz@users.noreply.github.com> Date: Mon, 23 Dec 2019 14:08:00 +0100 Subject: [PATCH 144/261] Fix timewindow parameters when zooming flot widget chart (#2271) --- ui/src/app/api/time.service.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js index 6b2e3f7687..56b7f0735b 100644 --- a/ui/src/app/api/time.service.js +++ b/ui/src/app/api/time.service.js @@ -281,6 +281,9 @@ function TimeService($translate, $http, $q, types) { var historyTimewindow = { + hideInterval: timewindow.hideInterval || false, + hideAggregation: timewindow.hideAggregation || false, + hideAggInterval: timewindow.hideAggInterval || false, history: { fixedTimewindow: { startTimeMs: startTimeMs, From 518e9ac9418c420ac951a41c0bd3387aa0ed2cf0 Mon Sep 17 00:00:00 2001 From: Oleg Kolesnik <31017535+jktu2870@users.noreply.github.com> Date: Mon, 23 Dec 2019 15:31:47 +0200 Subject: [PATCH 145/261] Control widgets background color (#2286) * UI:fix. Dashboard (widgets order mobile view) * UI:fix. Control widgets (background-color setting doesn't work for some of them) --- .../data/json/system/widget_bundles/control_widgets.json | 8 ++++---- ui/src/app/widget/lib/rpc/knob.scss | 3 --- ui/src/app/widget/lib/rpc/led-indicator.scss | 3 --- ui/src/app/widget/lib/rpc/round-switch.scss | 3 --- ui/src/app/widget/lib/rpc/switch.scss | 3 --- 5 files changed, 4 insertions(+), 16 deletions(-) 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 44f7d52ebf..9f1b8cea10 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 @@ -50,7 +50,7 @@ "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, { @@ -66,7 +66,7 @@ "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, { @@ -82,7 +82,7 @@ "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, { @@ -98,7 +98,7 @@ "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n if (self.ctx.resize) {\n self.ctx.resize();\n }\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", "dataKeySettingsSchema": "{}\n", - "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" + "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } }, { diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss index 568994d391..19009d9227 100644 --- a/ui/src/app/widget/lib/rpc/knob.scss +++ b/ui/src/app/widget/lib/rpc/knob.scss @@ -26,12 +26,9 @@ $minmax-height: percentage(.04) !default; $minmax-container-margin-pct: percentage(.18) !default; $minmax-container-margin-bottom-pct: percentage(.12) !default; -$background-color: #e6e7e8 !default; - .tb-knob { width: 100%; height: 100%; - background: $background-color; .knob { position: relative; diff --git a/ui/src/app/widget/lib/rpc/led-indicator.scss b/ui/src/app/widget/lib/rpc/led-indicator.scss index 7fe825dab7..4d058cffb9 100644 --- a/ui/src/app/widget/lib/rpc/led-indicator.scss +++ b/ui/src/app/widget/lib/rpc/led-indicator.scss @@ -17,12 +17,9 @@ $error-height: 14px !default; -$background-color: #e6e7e8 !default; - .tb-led-indicator { width: 100%; height: 100%; - background: $background-color; .title-container { .led-title { diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss index bc2cc4a929..50eaa6db75 100644 --- a/ui/src/app/widget/lib/rpc/round-switch.scss +++ b/ui/src/app/widget/lib/rpc/round-switch.scss @@ -17,12 +17,9 @@ $error-height: 14px !default; -$background-color: #e6e7e8 !default; - .tb-round-switch { width: 100%; height: 100%; - background: $background-color; .title-container { .switch-title { diff --git a/ui/src/app/widget/lib/rpc/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss index 72500ff03e..7045a4bf05 100644 --- a/ui/src/app/widget/lib/rpc/switch.scss +++ b/ui/src/app/widget/lib/rpc/switch.scss @@ -18,14 +18,11 @@ $thumb-checked-img: url("./svg/thumb-checked.svg") !default; $thumb-bar-img: url("./svg/thumb-bar.svg") !default; $thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default; -$background-color: #e6e7e8 !default; - $error-height: 14px !default; .tb-switch { width: 100%; height: 100%; - background: $background-color; .error-container { position: absolute; From 825406690c5d5896189ea3caf3e2abca65c94b40 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 23 Dec 2019 15:45:27 +0200 Subject: [PATCH 146/261] Flot widget pull requests: resolve conflicts for #2053, #2061 (#2289) * Add some datakey settings to edit legend appearance for flot widget * Fix timestamp and index search in 'line' flot charts tooltip Co-authored-by: Mirco Pizzichini <52463156+mircopz@users.noreply.github.com> --- ui/src/app/api/subscription.js | 3 +- ui/src/app/components/legend.directive.js | 4 +- ui/src/app/components/legend.tpl.html | 25 ++++++++--- ui/src/app/widget/lib/flot-widget.js | 55 ++++++++++++++++++++--- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 89f8aae309..4ad374bfe4 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -332,7 +332,8 @@ export default class Subscription { for (var a = 0; a < datasource.dataKeys.length; a++) { var dataKey = datasource.dataKeys[a]; - dataKey.hidden = false; + dataKey.hidden = dataKey.settings.hideDataByDefault ? true : false; + dataKey.inLegend = dataKey.settings.removeFromLegend ? false : true; dataKey.pattern = angular.copy(dataKey.label); if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) { diff --git a/ui/src/app/components/legend.directive.js b/ui/src/app/components/legend.directive.js index 76ffdb4e78..bed10e3f52 100644 --- a/ui/src/app/components/legend.directive.js +++ b/ui/src/app/components/legend.directive.js @@ -46,7 +46,9 @@ function Legend($compile, $templateCache, types) { scope.isRowDirection = scope.legendConfig.direction === types.direction.row.value; scope.toggleHideData = function(index) { - scope.legendData.keys[index].dataKey.hidden = !scope.legendData.keys[index].dataKey.hidden; + if (!scope.legendData.keys[index].dataKey.settings.disableDataHiding) { + scope.legendData.keys[index].dataKey.hidden = !scope.legendData.keys[index].dataKey.hidden; + } } $compile(element.contents())(scope); diff --git a/ui/src/app/components/legend.tpl.html b/ui/src/app/components/legend.tpl.html index 157835729b..d77bb1193c 100644 --- a/ui/src/app/components/legend.tpl.html +++ b/ui/src/app/components/legend.tpl.html @@ -27,7 +27,8 @@ - + - + {{ 'legend.min' | translate }} - {{ legendData.data[legendKey.dataIndex].min }} + + {{ legendData.data[legendKey.dataIndex].min }} + {{ 'legend.max' | translate }} - {{ legendData.data[legendKey.dataIndex].max }} + + {{ legendData.data[legendKey.dataIndex].max }} + {{ 'legend.avg' | translate }} - {{ legendData.data[legendKey.dataIndex].avg }} + + {{ legendData.data[legendKey.dataIndex].avg }} + {{ 'legend.total' | translate }} - {{ legendData.data[legendKey.dataIndex].total }} + + {{ legendData.data[legendKey.dataIndex].total }} + diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index 08496d574b..063c68a4da 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -139,12 +139,7 @@ export default class TbFlot { 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 timestamp = parseInt(found[0].time); let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); let dateDiv = $('
' + date + '
'); dateDiv.css({ @@ -1213,7 +1208,35 @@ export default class TbFlot { } static get pieDatakeySettingsSchema() { - return {} + return { + "schema": { + "type": "object", + "title": "DataKeySettings", + "properties": { + "hideDataByDefault": { + "title": "Data is hidden by default", + "type": "boolean", + "default": false + }, + "disableDataHiding": { + "title": "Disable data hiding", + "type": "boolean", + "default": false + }, + "removeFromLegend": { + "title": "Remove datakey from legend", + "type": "boolean", + "default": false + } + }, + "required": [] + }, + "form": [ + "hideDataByDefault", + "disableDataHiding", + "removeFromLegend" + ] + }; } static datakeySettingsSchema(defaultShowLines, chartType) { @@ -1228,6 +1251,21 @@ export default class TbFlot { "type": "boolean", "default": false }, + "hideDataByDefault": { + "title": "Data is hidden by default", + "type": "boolean", + "default": false + }, + "disableDataHiding": { + "title": "Disable data hiding", + "type": "boolean", + "default": false + }, + "removeFromLegend": { + "title": "Remove datakey from legend", + "type": "boolean", + "default": false + }, "showLines": { "title": "Show lines", "type": "boolean", @@ -1316,6 +1354,9 @@ export default class TbFlot { "required": ["showLines", "fillLines", "showPoints"] }, "form": [ + "hideDataByDefault", + "disableDataHiding", + "removeFromLegend", "excludeFromStacking", "showLines", "fillLines", From 6ba0b943e88118ee544988b7d8335f4b7aa46246 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 23 Dec 2019 17:35:04 +0200 Subject: [PATCH 147/261] Create new dataKey type entityField (#2282) * Add support import label * Create new dataKey type entityField * Add translate to entityField --- ui/src/app/api/entity.service.js | 43 ++++++++++ ui/src/app/api/subscription.js | 15 +++- ui/src/app/app.config.js | 5 +- ui/src/app/common/types.constant.js | 81 ++++++++++++++++++- ui/src/app/components/datakey-config.tpl.html | 6 +- .../components/datasource-entity.directive.js | 16 ++-- .../app/components/datasource-entity.tpl.html | 12 +++ .../widget/widget-config.directive.js | 8 +- ui/src/app/locale/locale.constant-el_GR.json | 18 ++++- ui/src/app/locale/locale.constant-en_US.json | 17 ++++ ui/src/app/locale/locale.constant-es_ES.json | 16 ++++ ui/src/app/locale/locale.constant-fr_FR.json | 16 ++++ ui/src/app/locale/locale.constant-ru_RU.json | 17 ++++ ui/src/app/locale/locale.constant-uk_UA.json | 17 ++++ 14 files changed, 268 insertions(+), 19 deletions(-) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 5ce4fd7108..51336ee374 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -848,7 +848,50 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } + function getEntityFieldKeys (entityType, searchText) { + let entityFieldKeys = []; + let query = searchText.toLowerCase(); + switch(entityType) { + case types.entityType.user: + entityFieldKeys.push(types.entityField.name.keyName); + entityFieldKeys.push(types.entityField.email.keyName); + entityFieldKeys.push(types.entityField.firstName.keyName); + entityFieldKeys.push(types.entityField.lastName.keyName); + break; + case types.entityType.tenant: + case types.entityType.customer: + entityFieldKeys.push(types.entityField.title.keyName); + entityFieldKeys.push(types.entityField.email.keyName); + entityFieldKeys.push(types.entityField.country.keyName); + entityFieldKeys.push(types.entityField.state.keyName); + entityFieldKeys.push(types.entityField.city.keyName); + entityFieldKeys.push(types.entityField.address.keyName); + entityFieldKeys.push(types.entityField.address2.keyName); + entityFieldKeys.push(types.entityField.zip.keyName); + entityFieldKeys.push(types.entityField.phone.keyName); + break; + case types.entityType.entityView: + entityFieldKeys.push(types.entityField.name.keyName); + entityFieldKeys.push(types.entityField.type.keyName); + break; + case types.entityType.device: + case types.entityType.asset: + entityFieldKeys.push(types.entityField.name.keyName); + entityFieldKeys.push(types.entityField.type.keyName); + entityFieldKeys.push(types.entityField.label.keyName); + break; + case types.entityType.dashboard: + entityFieldKeys.push(types.entityField.title.keyName); + break; + } + + return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys; + } + function getEntityKeys(entityType, entityId, query, type, config) { + if (type === types.dataKeyType.entityField) { + return $q.when(getEntityFieldKeys(entityType, query)); + } var deferred = $q.defer(); var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/'; if (type === types.dataKeyType.timeseries) { diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 4ad374bfe4..86ab823294 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -350,6 +350,11 @@ export default class Subscription { dataKey: dataKey, data: [] }; + if (dataKey.type === this.ctx.types.dataKeyType.entityField) { + if(datasource.entity && datasource.entity[this.ctx.types.entityField[dataKey.name].value]){ + datasourceData.data.push([Date.now(), datasource.entity[this.ctx.types.entityField[dataKey.name].value]]); + } + } this.data.push(datasourceData); this.hiddenData.push({data: []}); if (this.displayLegend) { @@ -878,8 +883,14 @@ export default class Subscription { }; } + var entityFieldKey = false; + for (var a = 0; a < datasource.dataKeys.length; a++) { - this.data[index + a].data = []; + if (datasource.dataKeys[a].type !== this.ctx.types.dataKeyType.entityField) { + this.data[index + a].data = []; + } else { + entityFieldKey = true; + } } index += datasource.dataKeys.length; @@ -891,7 +902,7 @@ export default class Subscription { } var forceUpdate = false; - if (datasource.unresolvedStateEntity || + if (datasource.unresolvedStateEntity || entityFieldKey || !datasource.dataKeys.length || (datasource.type === this.ctx.types.datasourceType.entity && !datasource.entityId) ) { diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js index c38deae93b..d441d87d8b 100644 --- a/ui/src/app/app.config.js +++ b/ui/src/app/app.config.js @@ -78,7 +78,8 @@ export default function AppConfig($provide, $mdIconProvider.iconSet('mdi', mdiIconSet); ngMdIconServiceProvider - .addShape('alpha-a-circle-outline', ''); + .addShape('alpha-a-circle-outline', '') + .addShape('alpha-e-circle-outline', ''); configureTheme(); @@ -170,4 +171,4 @@ export default function AppConfig($provide, return aliases; } -} \ No newline at end of file +} diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index e6e65bbecf..6b11af555f 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -322,7 +322,8 @@ export default angular.module('thingsboard.types', []) timeseries: "timeseries", attribute: "attribute", function: "function", - alarm: "alarm" + alarm: "alarm", + entityField: "entityField" }, contentType: { "JSON": { @@ -467,6 +468,84 @@ export default angular.module('thingsboard.types', []) list: 'entity.type-current-customer' } }, + entityField: { + createdTime: { + keyName: 'createdTime', + name: 'entity-field.created-time', + value: 'createdTime', + time: true + }, + name: { + keyName: 'name', + name: 'entity-field.name', + value: 'name' + }, + type: { + keyName: 'type', + name: 'entity-field.type', + value: 'type' + }, + firstName: { + keyName: 'firstName', + name: 'entity-field.first-name', + value: 'firstName' + }, + lastName: { + keyName: 'lastName', + name: 'entity-field.last-name', + value: 'lastName' + }, + email: { + keyName: 'email', + name: 'entity-field.email', + value: 'email' + }, + title: { + keyName: 'title', + name: 'entity-field.title', + value: 'title' + }, + country: { + keyName: 'country', + name: 'entity-field.country', + value: 'country' + }, + state: { + keyName: 'state', + name: 'entity-field.state', + value: 'state' + }, + city: { + keyName: 'city', + name: 'entity-field.city', + value: 'city' + }, + address: { + keyName: 'address', + name: 'entity-field.address', + value: 'address' + }, + address2: { + keyName: 'address2', + name: 'entity-field.address2', + value: 'address2' + }, + zip: { + keyName: 'zip', + name: 'entity-field.zip', + value: 'zip' + }, + phone: { + keyName: 'phone', + name: 'entity-field.phone', + value: 'phone' + }, + label: { + keyName: 'label', + name: 'entity-field.label', + value: 'label' + } + }, entitySearchDirection: { from: "FROM", to: "TO" diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html index 0b5b11d690..f8da179141 100644 --- a/ui/src/app/components/datakey-config.tpl.html +++ b/ui/src/app/components/datakey-config.tpl.html @@ -16,9 +16,7 @@ --> - - \ No newline at end of file + diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js index 9ed05b8812..844e60e97e 100644 --- a/ui/src/app/components/datasource-entity.directive.js +++ b/ui/src/app/components/datasource-entity.directive.js @@ -124,7 +124,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc var alarmDataKeys = []; for (var d in ngModelCtrl.$viewValue.dataKeys) { var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; - if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) { + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute) || (dataKey.type === types.dataKeyType.entityField)) { dataKeys.push(dataKey); } else if (dataKey.type === types.dataKeyType.alarm) { alarmDataKeys.push(dataKey); @@ -219,7 +219,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc w.triggerHandler('resize'); } }).then(function (newDataKey) { - if ((newDataKey.type === types.dataKeyType.timeseries) || (newDataKey.type === types.dataKeyType.attribute)) { + if ((newDataKey.type === types.dataKeyType.timeseries) || (newDataKey.type === types.dataKeyType.attribute) || (newDataKey.type === types.dataKeyType.entityField)) { let index = scope.dataKeys.indexOf(dataKey); scope.dataKeys[index] = newDataKey; } else if (newDataKey.type === types.dataKeyType.alarm) { @@ -246,10 +246,16 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc items.push({ name: dataKeys[i], type: types.dataKeyType.timeseries }); } if (scope.widgetType == types.widgetType.latest.value) { - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.attribute}) - .then(function (dataKeys) { + var keysType = [types.dataKeyType.attribute, types.dataKeyType.entityField]; + var promises = []; + keysType.forEach((type) => { + promises.push(scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type})); + }); + $q.all(promises).then(function (dataKeys) { for (var i = 0; i < dataKeys.length; i++) { - items.push({ name: dataKeys[i], type: types.dataKeyType.attribute }); + for (var j = 0; j < dataKeys[i].length; j++) { + items.push({name: dataKeys[i][j], type: keysType[i]}); + } } deferred.resolve(items); }, function (e) { diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index fe0d23eef7..a167d313ee 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -43,6 +43,10 @@ {{'datakey.attributes' | translate }}
+ + {{'datakey.entityField' | translate }} + + {{'datakey.timeseries' | translate }} @@ -60,6 +64,10 @@ {{'datakey.attributes' | translate }} + + {{'datakey.entityField' | translate }} + + {{'datakey.timeseries' | translate }} @@ -81,6 +89,10 @@ {{'datakey.attributes' | translate }} + + + {{'datakey.entityField' | translate }} + {{'datakey.timeseries' | translate }} diff --git a/ui/src/app/components/widget/widget-config.directive.js b/ui/src/app/components/widget/widget-config.directive.js index 470726a835..2fe97ce6d6 100644 --- a/ui/src/app/components/widget/widget-config.directive.js +++ b/ui/src/app/components/widget/widget-config.directive.js @@ -423,10 +423,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout } var label = chip; - if (type === types.dataKeyType.alarm) { - var alarmField = types.alarmFields[chip]; - if (alarmField) { - label = $translate.instant(alarmField.name)+''; + if (type === types.dataKeyType.alarm || type === types.dataKeyType.entityField) { + var keyField = type === types.dataKeyType.alarm ? types.alarmFields[chip] : types.entityField[chip]; + if (keyField) { + label = $translate.instant(keyField.name)+''; } } label = scope.genNextLabel(label); diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index b590c70e47..f3051e98c1 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -1102,6 +1102,22 @@ "copyId": "Αντιγραφή ID ομάδας οντοτήτων", "idCopiedMessage": "Το ID της ομάδας οντοτήτων έχει αντιγραφεί στο πρόχειρο" }, + "entity-field": { + "created-time": "Δημιουργήθηκε", + "name": "Όνομα", + "type": "Τύπος", + "first-name": "Όνομα", + "last-name": "Επίθετο", + "email": "Email", + "title": "Τίτλος", + "country": "Χώρα", + "state": "Νομός", + "city": "Πόλη", + "address": "Διεύθυνση", + "address2": "Διεύθυνση 2", + "zip": "Τ.Κ.", + "phone": "Τηλέφωνο" + }, "entity-view": { "entity-view": "Όψη Οντότητας", "entity-view-required": "Απαιτείται προβολή οντότητας.", @@ -2603,4 +2619,4 @@ "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 67f3fb09de..6310438128 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -815,6 +815,23 @@ "no-data": "No data to display", "columns-to-display": "Columns to Display" }, + "entity-field": { + "created-time": "Created time", + "name": "Name", + "type": "Type", + "first-name": "First name", + "last-name": "Last name", + "email": "Email", + "title": "Title", + "country": "Country", + "state": "State", + "city": "City", + "address": "Address", + "address2": "Address 2", + "zip": "Zip", + "phone": "Phone", + "label": "Label" + }, "entity-view": { "entity-view": "Entity View", "entity-view-required": "Entity view is required.", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index d3d1c4042f..5cebe22ef6 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -808,6 +808,22 @@ "no-data": "No hay datos para mostrar", "columns-to-display": "Columnas a mostrar" }, + "entity-field": { + "created-time": "Tiempo de creación", + "name": "Nombre", + "type": "Tipo", + "first-name": "Nombre", + "last-name": "Apellido", + "email": "Correo electrónico", + "title": "Título", + "country": "País", + "state": "Estado", + "city": "Ciudad", + "address": "Dirección", + "address2": "Dirección 2", + "zip": "Código postal", + "phone": "Teléfono" + }, "entity-view": { "entity-view": "Vista de entidad", "entity-view-required": "Vista de entidad es requerido.", diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index b25a532927..5c922bfd49 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -809,6 +809,22 @@ "use-entity-name-filter": "Utiliser un filtre", "user-name-starts-with": "Utilisateurs dont les noms commencent par '{{prefix}}'" }, + "entity-field": { + "address": "Adresse", + "address2": "Adresse 2", + "city": "Ville", + "country": "Pays", + "created-time": "Heure de création", + "email": "Email", + "first-name": "Prénom", + "last-name": "Nom de famille", + "name": "Nom", + "phone": "Téléphone", + "state": "Prov", + "title": "Titre", + "type": "Type", + "zip": "Code postal" + }, "entity-view": { "add": "Ajouter une vue d'entité", "add-alias": "Ajouter un alias de vue d'entité", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 3aaa3042c5..ca4570638a 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -814,6 +814,23 @@ "no-data": "Нет данных для отображения", "columns-to-display": "Отобразить следующие колонки" }, + "entity-field": { + "created-time": "Время создания", + "name": "Название", + "type": "Тип", + "first-name": "Имя", + "last-name": "Фамилия", + "email": "Электронная почта", + "title": "Название", + "country": "Страна", + "state": "Штат/Область", + "city": "Город", + "address": "Адрес", + "address2": "Адрес 2", + "zip": "Индекс", + "phone": "Телефон", + "label": "Метка" + }, "entity-view": { "entity-view": "Представление Объекта", "entity-view-required": "Представление объекта обязательно.", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index cbbaabd666..49fd45a409 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -956,6 +956,23 @@ "list-of-integrations": "{ count, plural, 1 {Одна інтеграція} other {Список # інтеграцій} }", "integration-name-starts-with": "Інтеграції, імена яких починаються з '{{prefix}}'" }, + "entity-field": { + "created-time": "Час створення", + "name": "Ім'я", + "type": "Тип", + "first-name": "Ім'я", + "last-name": "Прізвище", + "email": "Електронна пошта", + "title": "Назва", + "country": "Країна", + "state": "Штат", + "city": "Місто", + "address": "Адреса", + "address2": "Адреса 2", + "zip": "Zip", + "phone": "Телефон", + "label": "Мітка" + }, "entity-group": { "entity-group": "Група сутності", "details": "Деталі", From a93c8e25bac71379dd345547a48daf8ec99a4dee Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 23 Dec 2019 17:35:49 +0200 Subject: [PATCH 148/261] Hide timewindow when all options are hidden (#2287) * Hide timewindow when all options are hidden * Disable timewindow button when all options hidden --- ui/src/app/components/timewindow-button.tpl.html | 2 +- ui/src/app/components/timewindow.directive.js | 7 ++++++- ui/src/app/components/timewindow.tpl.html | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ui/src/app/components/timewindow-button.tpl.html b/ui/src/app/components/timewindow-button.tpl.html index 6edf9c76a0..5549fd35e6 100644 --- a/ui/src/app/components/timewindow-button.tpl.html +++ b/ui/src/app/components/timewindow-button.tpl.html @@ -15,7 +15,7 @@ limitations under the License. --> - + {{model.displayValue}} \ No newline at end of file diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js index 0d06de8679..991a3838ff 100644 --- a/ui/src/app/components/timewindow.directive.js +++ b/ui/src/app/components/timewindow.directive.js @@ -97,7 +97,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM element.html(template); scope.openEditMode = function (event) { - if (scope.disabled) { + if (scope.timewindowDisabled) { return; } var position; @@ -212,6 +212,10 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM } } + scope.isTimewindowDisabled = function () { + return scope.disabled || (!scope.isEdit && scope.model.hideInterval && scope.model.hideAggregation && scope.model.hideAggInterval); + } + ngModelCtrl.$render = function () { scope.model = timeService.defaultTimewindow(); if (ngModelCtrl.$viewValue) { @@ -243,6 +247,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM model.hideAggregation = value.hideAggregation; model.hideAggInterval = value.hideAggInterval; } + scope.timewindowDisabled = scope.isTimewindowDisabled(); scope.updateDisplayValue(); }; diff --git a/ui/src/app/components/timewindow.tpl.html b/ui/src/app/components/timewindow.tpl.html index 0e0475b9cd..2889addd5a 100644 --- a/ui/src/app/components/timewindow.tpl.html +++ b/ui/src/app/components/timewindow.tpl.html @@ -16,7 +16,7 @@ -->
- + {{ 'timewindow.edit' | translate }} @@ -28,7 +28,7 @@ {{model.displayValue}} - + {{ 'timewindow.edit' | translate }} From a490f02a29eb28e07367a359d7c13a0e0a5229c7 Mon Sep 17 00:00:00 2001 From: Albert Yurgin <36601239+Scvoll@users.noreply.github.com> Date: Mon, 23 Dec 2019 18:36:29 +0300 Subject: [PATCH 149/261] fixed interpolation error (#2290) --- ui/src/app/locale/locale.constant-ru_RU.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index ca4570638a..41fa69a3dc 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -661,7 +661,7 @@ "delete-device-title": "Вы точно хотите удалить устройство '{{deviceName}}'?", "delete-device-text": "Внимание, после подтверждения устройство и все связанные с ним данные будут безвозвратно утеряны.", "delete-devices-title": "Вы точно хотите удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }?", - "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} } }", + "delete-devices-action-title": "Удалить { count, plural, one {1 устройство} few {# устройства} other {# устройств} }", "delete-devices-text": "Внимание, после подтверждения выбранные устройства и все связанные с ними данные будут безвозвратно утеряны..", "unassign-device-title": "Вы точно хотите отозвать устройство '{{deviceName}}'?", "unassign-device-text": "После подтверждения устройство будет недоступно клиенту.", From 7bdbdad287df96b2b5121b97c60af3ad8656c8a9 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Dec 2019 17:46:48 +0200 Subject: [PATCH 150/261] upgrade from version 2.4.2 --- .../server/install/ThingsboardInstallService.java | 6 +++--- .../service/install/CassandraDatabaseUpgradeService.java | 2 +- .../server/service/install/SqlDatabaseUpgradeService.java | 2 +- 3 files changed, 5 insertions(+), 5 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 744fb368a9..b58a2c42e5 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -123,10 +123,10 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); databaseUpgradeService.upgradeDatabase("2.4.1"); - case "2.4.2.1": - log.info("Upgrading ThingsBoard from version 2.4.2.1 to 2.4.3 ..."); + case "2.4.2": + log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ..."); - databaseUpgradeService.upgradeDatabase("2.4.2.1"); + databaseUpgradeService.upgradeDatabase("2.4.2"); log.info("Updating system data..."); 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 7d0b22db67..01fb0c083d 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 @@ -278,7 +278,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { } catch (InvalidQueryException e) {} log.info("Schema updated."); break; - case "2.4.2.1": + case "2.4.2": log.info("Updating schema ..."); String updateAlarmTableStmt = "alter table alarm add propagate_relation_types text"; try { 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 d686b172cf..b9e9504462 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 @@ -196,7 +196,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Schema updated."); } break; - case "2.4.2.1": + case "2.4.2": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); try { From 0ef930e43aef3945b51857d1613057734a95fcd3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Dec 2019 18:28:46 +0200 Subject: [PATCH 151/261] Set version --- msa/js-executor/package.json | 2 +- msa/web-ui/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 0cdafffef1..6e92570628 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.5.0", + "version": "2.3.0", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 176791d65a..b66db7c2ac 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.5.0", + "version": "2.3.0", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/ui/package.json b/ui/package.json index 4c67341178..04ebcec1eb 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard", "private": true, - "version": "2.5.0", + "version": "2.3.0", "description": "ThingsBoard UI", "licenses": [ { From 10777861a0c60d164e18a34fc31039e97ab54c5c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 23 Dec 2019 18:31:44 +0200 Subject: [PATCH 152/261] Set version --- msa/js-executor/package.json | 2 +- msa/web-ui/package.json | 2 +- ui/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 6e92570628..c87b62c6eb 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.3.0", + "version": "2.4.3", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index b66db7c2ac..188084d375 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.3.0", + "version": "2.4.3", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/ui/package.json b/ui/package.json index 04ebcec1eb..f9f6083b31 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard", "private": true, - "version": "2.3.0", + "version": "2.4.3", "description": "ThingsBoard UI", "licenses": [ { From aa7c946cb835494a7b460c796d1bc9540354ceec Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 23 Dec 2019 18:41:49 +0200 Subject: [PATCH 153/261] Improvement/new datakey types (#2291) * Add support import label * Add translate tooltip --- ui/src/app/components/datasource-entity.tpl.html | 6 +++--- ui/src/app/locale/locale.constant-en_US.json | 1 + ui/src/app/locale/locale.constant-ru_RU.json | 1 + ui/src/app/locale/locale.constant-uk_UA.json | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index a167d313ee..df06e9465f 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -44,7 +44,7 @@ - {{'datakey.entityField' | translate }} + {{'datakey.entity-field' | translate }} @@ -65,7 +65,7 @@ - {{'datakey.entityField' | translate }} + {{'datakey.entity-field' | translate }} @@ -91,7 +91,7 @@ - {{'datakey.entityField' | translate }} + {{'datakey.entity-field' | translate }} diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 6310438128..f6a42cd957 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -583,6 +583,7 @@ "configuration": "Data key configuration", "timeseries": "Timeseries", "attributes": "Attributes", + "entity-field": "Entity field", "alarm": "Alarm fields", "timeseries-required": "Entity timeseries are required.", "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 41fa69a3dc..25257232d0 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -583,6 +583,7 @@ "configuration": "Конфигурация ключа данных", "timeseries": "Телеметрия", "attributes": "Атрибуты", + "entity-field": "Поле объекта", "alarm": "Параметры оповещения", "timeseries-required": "Телеметрия объекта обязательна.", "timeseries-or-attributes-required": "Телеметрия/атрибуты обязательны.", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 49fd45a409..bc64b52901 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -699,6 +699,7 @@ "configuration": "Конфігурація ключа даних", "timeseries": "Телеметрія", "attributes": "Атрибути", + "entity-field": "Поле сутності", "alarm": "Поля сигнала тривоги", "timeseries-required": "Необхідно вказати Телеметрія.", "timeseries-or-attributes-required": "Необхідно вказати телеметрію/атрибути.", From 04f18a6fe8a30c66ada598a6503600e3d1afb8fd Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Tue, 24 Dec 2019 13:30:02 +0200 Subject: [PATCH 154/261] Bugs entityFields dataKey (#2293) * Add support import label * Fix not load and show first dataKey --- ui/src/app/api/entity.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 51336ee374..b71fcfe75e 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -850,7 +850,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device function getEntityFieldKeys (entityType, searchText) { let entityFieldKeys = []; - let query = searchText.toLowerCase(); + let query = searchText ? searchText.toLowerCase() : ""; switch(entityType) { case types.entityType.user: entityFieldKeys.push(types.entityField.name.keyName); From 8d45f32d8d78b436f57de43f7bf2928724bf0399 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Dec 2019 14:04:04 +0200 Subject: [PATCH 155/261] Improve entity state controller. --- .../states/entity-state-controller.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js index a4c6cb7d7c..4762af0942 100644 --- a/ui/src/app/dashboard/states/entity-state-controller.js +++ b/ui/src/app/dashboard/states/entity-state-controller.js @@ -44,8 +44,7 @@ export default function EntityStateController($scope, $timeout, $location, $stat function openState(id, params, openRightLayout) { if (vm.states && vm.states[id]) { resolveEntity(params).then( - function success(entityName) { - params.entityName = entityName; + function success() { var newState = { id: id, params: params @@ -66,8 +65,7 @@ export default function EntityStateController($scope, $timeout, $location, $stat } if (vm.states && vm.states[id]) { resolveEntity(params).then( - function success(entityName) { - params.entityName = entityName; + function success() { var newState = { id: id, params: params @@ -183,16 +181,16 @@ export default function EntityStateController($scope, $timeout, $location, $stat params = params[params.targetEntityParamName]; } if (params && params.entityId && params.entityId.id && params.entityId.entityType) { - if (params.entityName && params.entityName.length) { - deferred.resolve(params.entityName); + if (isEntityResolved(params)) { + deferred.resolve(); } else { entityService.getEntity(params.entityId.entityType, params.entityId.id, { ignoreLoading: true, ignoreErrors: true }).then( function success(entity) { - var entityName = entity.name; - deferred.resolve(entityName); + params.entityName = entity.name; + deferred.resolve(); }, function fail() { deferred.reject(); @@ -200,11 +198,18 @@ export default function EntityStateController($scope, $timeout, $location, $stat ); } } else { - deferred.resolve(''); + deferred.resolve(); } return deferred.promise; } + function isEntityResolved(params) { + if (!params.entityName || !params.entityName.length) { + return false; + } + return true; + } + function parseState(stateBase64) { var result; if (stateBase64) { From 981f7443ce9f3f0ea1ff3d582dd1a693e78da2c6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 24 Dec 2019 14:23:35 +0200 Subject: [PATCH 156/261] Improve resolveAliasFilter method --- ui/src/app/api/entity.service.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index b71fcfe75e..7c656442f7 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -439,7 +439,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return entityId; } - function getStateEntityId(filter, stateParams) { + function getStateEntityInfo(filter, stateParams) { var entityId = null; if (stateParams) { if (filter.stateEntityParamName && filter.stateEntityParamName.length) { @@ -456,7 +456,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device if (entityId) { entityId = resolveAliasEntityId(entityId.entityType, entityId.id); } - return entityId; + return { + entityId: entityId + }; } function resolveAliasFilter(filter, stateParams, maxItems, failOnEmpty) { @@ -468,7 +470,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device if (filter.stateEntityParamName && filter.stateEntityParamName.length) { result.entityParamName = filter.stateEntityParamName; } - var stateEntityId = getStateEntityId(filter, stateParams); + var stateEntityInfo = getStateEntityInfo(filter, stateParams); + var stateEntityId = stateEntityInfo.entityId; switch (filter.type) { case types.aliasFilterType.singleEntity.value: var aliasEntityId = resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id); From 45a4c6f3423b7271d7f7e1b4dca95fd6fcdc1aab Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Tue, 24 Dec 2019 16:17:01 +0200 Subject: [PATCH 157/261] Improvement/state entity name (#2294) * Add support import label * Change enityName use targetEntityParam --- ui/src/app/dashboard/states/entity-state-controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/src/app/dashboard/states/entity-state-controller.js b/ui/src/app/dashboard/states/entity-state-controller.js index 4762af0942..b5f9762708 100644 --- a/ui/src/app/dashboard/states/entity-state-controller.js +++ b/ui/src/app/dashboard/states/entity-state-controller.js @@ -164,7 +164,12 @@ export default function EntityStateController($scope, $timeout, $location, $stat var stateName = vm.states[vm.stateObject[index].id].name; stateName = utils.customTranslation(stateName, stateName); var params = vm.stateObject[index].params; - var entityName = params && params.entityName ? params.entityName : ''; + var entityName; + if (params && params.targetEntityParamName && params[params.targetEntityParamName].entityName) { + entityName = params[params.targetEntityParamName].entityName; + } else { + entityName = params && params.entityName ? params.entityName : ''; + } result = utils.insertVariable(stateName, 'entityName', entityName); for (var prop in params) { if (params[prop] && params[prop].entityName) { From 92c1374a3f31a31b9492a192d65a740b67352c16 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 26 Dec 2019 17:53:10 +0200 Subject: [PATCH 158/261] Fix knob control widget (#2298) * Add support import label * Fix knob control widget --- ui/src/app/app.run.js | 2 -- ui/src/app/widget/lib/rpc/knob.directive.js | 5 ++++- ui/src/app/widget/lib/rpc/knob.tpl.html | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js index 5bb8131e1e..0ca44316c1 100644 --- a/ui/src/app/app.run.js +++ b/ui/src/app/app.run.js @@ -162,14 +162,12 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, if (forbiddenDialog === null) { $translate(['access.access-forbidden', 'access.access-forbidden-text', - 'access.access-forbidden', 'action.cancel', 'action.sign-in']).then(function (translations) { if (forbiddenDialog === null) { forbiddenDialog = $mdDialog.confirm() .title(translations['access.access-forbidden']) .htmlContent(translations['access.access-forbidden-text']) - .ariaLabel(translations['access.access-forbidden']) .cancel(translations['action.cancel']) .ok(translations['action.sign-in']); $mdDialog.show(forbiddenDialog).then(function () { diff --git a/ui/src/app/widget/lib/rpc/knob.directive.js b/ui/src/app/widget/lib/rpc/knob.directive.js index 4bd4a58288..d26e4a8d09 100644 --- a/ui/src/app/widget/lib/rpc/knob.directive.js +++ b/ui/src/app/widget/lib/rpc/knob.directive.js @@ -328,6 +328,9 @@ function KnobController($element, $scope, $document) { var textWidth = measureTextWidth(text, fontSize); while (textWidth > maxWidth) { fontSize--; + if (fontSize < 0) { + break; + } textWidth = measureTextWidth(text, fontSize); } element.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'}); @@ -335,7 +338,7 @@ function KnobController($element, $scope, $document) { function measureTextWidth(text, fontSize) { textMeasure.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'}); - textMeasure.text(text); + textMeasure.html(text); return textMeasure.width(); } diff --git a/ui/src/app/widget/lib/rpc/knob.tpl.html b/ui/src/app/widget/lib/rpc/knob.tpl.html index 290b1920a7..45cc766302 100644 --- a/ui/src/app/widget/lib/rpc/knob.tpl.html +++ b/ui/src/app/widget/lib/rpc/knob.tpl.html @@ -29,7 +29,7 @@
- {{ vm.error }} +
{{ vm.title }} @@ -42,4 +42,4 @@
- \ No newline at end of file + From 41a1c6679fb147f6f2442cb7a4563e37ea8c7073 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 27 Dec 2019 18:06:31 +0200 Subject: [PATCH 159/261] Added support entity Label to state name and breadcrumb for dashboard --- ui/src/app/api/subscription.js | 8 ++++--- .../components/widget/widget.controller.js | 15 ++++++++---- .../states/entity-state-controller.js | 23 +++++++++++-------- .../app/widget/lib/entities-table-widget.js | 12 +++++----- ui/src/app/widget/lib/flot-widget.js | 3 ++- ui/src/app/widget/lib/map-widget2.js | 9 +++++--- .../app/widget/lib/timeseries-table-widget.js | 10 ++++---- 7 files changed, 49 insertions(+), 31 deletions(-) diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index 86ab823294..f3d8e5cf47 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -179,8 +179,7 @@ export default class Subscription { } getFirstEntityInfo() { - var entityId; - var entityName; + var entityId, entityName, entityLabel = null; if (this.type === this.ctx.types.widgetType.rpc.value) { if (this.targetDeviceId) { entityId = { @@ -196,6 +195,7 @@ export default class Subscription { id: this.alarmSource.entityId }; entityName = this.alarmSource.entityName; + entityLabel = this.alarmSource.entityLabel; } } else { for (var i=0;i Date: Mon, 6 Jan 2020 16:35:48 +0200 Subject: [PATCH 160/261] Fix: Add entityLabel to handleWidgetAction --- ui/src/app/components/widget/widget.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index d093e0c1bd..b9f1317528 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -490,7 +490,7 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL } } - function handleWidgetAction($event, descriptor, entityId, entityName, additionalParams) { + function handleWidgetAction($event, descriptor, entityId, entityName, additionalParams, entityLabel) { var type = descriptor.type; var targetEntityParamName = descriptor.stateEntityParamName; var targetEntityId; From 168f0d64813b7bea4b343beaaef3ee2c59c97ce9 Mon Sep 17 00:00:00 2001 From: Li-Heng Yu <007seadog@gmail.com> Date: Mon, 6 Jan 2020 22:39:04 +0800 Subject: [PATCH 161/261] Added Traditional Chinese Interface for Web UI (#2303) * Fixed other languages name in zh_CN ui * Added Traditional Chinese for UI * Added language name of Traditional Chinese for en_US pack * Fixed zh_TW language pack and updated language usage --- ui/src/app/locale/locale.constant-en_US.json | 3 +- ui/src/app/locale/locale.constant-zh_CN.json | 27 +- ui/src/app/locale/locale.constant-zh_TW.json | 1622 ++++++++++++++++++ 3 files changed, 1638 insertions(+), 14 deletions(-) create mode 100644 ui/src/app/locale/locale.constant-zh_TW.json diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index f6a42cd957..dde68c35b4 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1794,7 +1794,8 @@ "locales": { "de_DE": "German", "fr_FR": "French", - "zh_CN": "Chinese", + "zh_CN": "Simplified Chinese", + "zh_TW": "Traditional Chinese", "en_US": "English", "it_IT": "Italian", "ko_KR": "Korean", diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index a7f1cb9e62..0c1f5f743b 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -1603,19 +1603,20 @@ "language": { "language": "语言", "locales": { - "de_DE": "德语", - "en_US": "英语", - "fr_FR": "法国", - "ko_KR": "韩语", - "zh_CN": "汉语", - "ru_RU": "俄语", - "es_ES": "西班牙语", - "it_IT": "意大利", - "ja_JA": "日本", - "tr_TR": "土耳其", - "fa_IR": "波斯语", - "uk_UA": "乌克兰", - "cs_CZ": "在捷克" + "de_DE": "德文", + "en_US": "英文", + "fr_FR": "法文", + "ko_KR": "韩文", + "zh_CN": "简体中文", + "zh_TW": "繁体中文", + "ru_RU": "俄文", + "es_ES": "西班牙文", + "it_IT": "意大利文", + "ja_JA": "日文", + "tr_TR": "土耳其文", + "fa_IR": "波斯文", + "uk_UA": "乌克兰文", + "cs_CZ": "捷克文" } } } diff --git a/ui/src/app/locale/locale.constant-zh_TW.json b/ui/src/app/locale/locale.constant-zh_TW.json new file mode 100644 index 0000000000..acaa9ac53c --- /dev/null +++ b/ui/src/app/locale/locale.constant-zh_TW.json @@ -0,0 +1,1622 @@ +{ +    "access": { +        "unauthorized": "未授權", +        "unauthorized-access": "未授權存取", +        "unauthorized-access-text": "您需要登入才能存取這個資源!", +        "access-forbidden": "禁止存取", +        "access-forbidden-text": "您沒有存取此位置的權限
如果您仍希望存取此位置,請嘗試使用其他用戶登入。", +        "refresh-token-expired": "Session 已過期", +        "refresh-token-failed": "無法更新 Session" +    }, +    "action": { +        "activate": "啟動", +        "suspend": "暫停", +        "save": "儲存", +        "saveAs": "另存為", +        "cancel": "取消", +        "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": "拖拉", +        "refresh": "更新", +        "undo": "取消", +        "copy": "複製", +        "paste": "貼上", +        "copy-reference": "複製引用", +        "paste-reference": "貼上引用", +        "import": "匯入", +        "export": "匯出", +        "share-via": "通過 {{provider}}分享" +    }, +    "aggregation": { +        "aggregation": "聚合", +        "function": "資料聚合功能", +        "limit": "最大值", +        "group-interval": "分組間隔", +        "min": "最少值", +        "max": "最大值", +        "avg": "平均值", +        "sum": "總計", +        "count": "Count", +        "none": "空" +    }, +    "admin": { +        "general": "一般", +        "general-settings": "一般設定", +        "outgoing-mail": "發送郵件", +        "outgoing-mail-settings": "發送郵件設定", +        "system-settings": "系統設定", +        "test-mail-sent": "測試郵件發送成功!", +        "base-url": "基本URL", +        "base-url-required": "基本URL必填。", +        "mail-from": "郵件來自", +        "mail-from-required": "郵件發件人必填。", +        "smtp-protocol": "SMTP協定", +        "smtp-host": "SMTP主機", +        "smtp-host-required": "SMTP主機必填。", +        "smtp-port": "SMTP連接埠", +        "smtp-port-required": "您必須提供一個smtp連接埠。", +        "smtp-port-invalid": "這看起來不是有效的smtp連接埠。", +        "timeout-msec": "超時(ms)", +        "timeout-required": "超時必填。", +        "timeout-invalid": "這看起來不像有效的超時值。", +        "enable-tls": "啟用TLS", +        "send-test-mail": "發送測試郵件" +    }, +    "alarm": { +        "alarm": "警告", +        "alarms": "警告", +        "select-alarm": "選擇警告", +        "no-alarms-matching": "沒有找到符合 '{{entity}}' 的警告", +        "alarm-required": "警告必填", +        "alarm-status": "警告狀態", +        "search-status": { +            "ANY": "所有", +            "ACTIVE": "已啟動", +            "CLEARED": "已清除", +            "ACK": "已回覆", +            "UNACK": "未回覆" +        }, +        "display-status": { +            "ACTIVE_UNACK": "啟動未回覆", +            "ACTIVE_ACK": "啟動已回覆", +            "CLEARED_UNACK": "清除未回覆", +            "CLEARED_ACK": "清除已回覆" +        }, +        "no-alarms-prompt": "未發現警告", +        "created-time": "建立時間", +        "type": "類型", +        "severity": "嚴重程度", +        "originator": "起因", +        "originator-type": "起因類型", +        "details": "詳細資訊", +        "status": "狀態", +        "alarm-details": "警告詳細資訊", +        "start-time": "開始時間", +        "end-time": "結束時間", +        "ack-time": "回覆時間", +        "clear-time": "建立時間", +        "severity-critical": "危險", +        "severity-major": "重要", +        "severity-minor": "次要", +        "severity-warning": "警告", +        "severity-indeterminate": "不確定", +        "acknowledge": "回覆", +        "clear": "清除", +        "search": "搜尋警告", +        "selected-alarms": "已選擇 { count, plural, 1 {1 警告} other {# 警告} } ", +        "no-data": "無資料顯示", +        "polling-interval": "警告輪詢間隔(秒)", +        "polling-interval-required": "警告輪詢間隔必填。", +        "min-polling-interval-message": "輪詢間隔至少是1秒。", +        "aknowledge-alarms-title": "回覆 { count, plural, 1 {1 警告} other {# 警告} }", +        "aknowledge-alarms-text": "確定要回覆 { count, plural, 1 {1 警告} other {# 警告} }?", +        "clear-alarms-title": "清除 { count, plural, 1 {1 警告} other {# 警告} }", +        "clear-alarms-text": "確定要清除 { count, plural, 1 {1 警告} other {# 警告} }?" +    }, +    "alias": { +        "add": "增加別名", +        "edit": "編輯別名", +        "name": "別名", +        "name-required": "別名必填", +        "duplicate-alias": "別名已經存在。", +        "filter-type-single-entity": "單一實體", +        "filter-type-entity-list": "實體列表", +        "filter-type-entity-name": "實體名稱", +        "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}}' 開頭的資產", +        "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": "具有 {{relationType}} 關聯 {{direction}} {{rootEntity}} 的 {{entities}} ", +        "filter-type-asset-search-query": "資產搜尋查詢", +        "filter-type-asset-search-query-description": "類型為 {{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": "使用儀表板狀態實體作為根實體", +        "root-entity": "根實體", +        "state-entity-parameter-name": "狀態實體參數名稱", +        "default-state-entity": "預設狀態實體", +        "default-entity-parameter-name": "預設", +        "max-relation-level": "最大關係層級", +        "unlimited-level": "不限層級", +        "state-entity": "儀表板狀態實體", +        "all-entities": "所有實體", +        "any-relation": "不限" +    }, +    "asset": { +        "asset": "資產", +        "assets": "資產", +        "management": "資產管理", +        "view-assets": "查看資產", +        "add": "增加資產", +        "assign-to-customer": "分配給客戶", +        "assign-asset-to-customer": "將資產分配給客戶", +        "assign-asset-to-customer-text": "請選擇要分配給客戶的資產", +        "no-assets-text": "未找到資產", +        "assign-to-customer-text": "請選擇客戶以分配資產", +        "public": "公開", +        "assignedToCustomer": "分配客戶", +        "make-public": "資產設為公開", +        "make-private": "資產設為私有", +        "unassign-from-customer": "取消分配客戶", +        "delete": "刪除資產", +        "asset-public": "資產公開", +        "asset-type": "資產類型", +        "asset-type-required": "資產類型必填。", +        "select-asset-type": "選擇資產類型", +        "enter-asset-type": "輸入資產類型", +        "any-asset": "任何資產", +        "no-asset-types-matching": "沒有找到符合 '{{entitySubtype}}' 的資產類型。", +        "asset-type-list-empty": "資產類型未選擇。", +        "asset-types": "資產類型", +        "name": "名稱", +        "name-required": "名稱必填。", +        "description": "描述", +        "type": "類型", +        "type-required": "類型必填。", +        "details": "詳細資訊", +        "events": "事件", +        "add-asset-text": "增加新資產", +        "asset-details": "資產詳細資訊", +        "assign-assets": "分配資產", +        "assign-assets-text": "分配 { count, plural, 1 {1 資產} other {# 資產} } 給客戶", +        "delete-assets": "刪除資產", +        "unassign-assets": "取消分配資產", +        "unassign-assets-action-title": "從客戶處取消分配 { count, plural, 1 {1 資產} other {# 資產} } ", +        "assign-new-asset": "分配新資產", +        "delete-asset-title": "確定要刪除資產 '{{assetName}}'?", +        "delete-asset-text": "小心!確認後資產及其所有相關資料將無法恢復。", +        "delete-assets-title": "確定要刪除 { count, plural, 1 {1 資產} other {# 資產} }?", +        "delete-assets-action-title": "刪除 { count, plural, 1 {1 資產} other {# 資產} }", +        "delete-assets-text": "小心,確認後,所有選擇的資產將被刪除,所有相關的資料將變得無法恢復。", +        "make-public-asset-title": "你確定你想建立公開'{{assetName}}'資產?", +        "make-public-asset-text": "確認後,資產及其所有資料將被公開並被他人存取。", +        "make-private-asset-title": "你確定你想建立私有 '{{assetName}}' 資產?", +        "make-private-asset-text": "確認後,資產及其所有資料將被私有化,無法被他人存取。", +        "unassign-asset-title": "您確定要取消對'{{assetName}}'資產的分配嗎?", +        "unassign-asset-text": "確認後,資產將未分配,客戶無法存取。", +        "unassign-asset": "未分配資產", +        "unassign-assets-title": "您確定要取消分配 { count, plural, 1 {1 資產} other {# 資產} }嗎?", +        "unassign-assets-text": "確認後,所有選擇的資產將被分配,客戶無法存取。", +        "copyId": "複製資產ID", +        "idCopiedMessage": "資產ID已經複製到剪貼簿", +        "select-asset": "選擇資產", +        "no-assets-matching": "沒有找到符合 '{{entity}}' 的資產。", +        "asset-required": "資產必填", +        "name-starts-with": "資產名稱以此開頭", +        "label": "標籤" +    }, +    "attribute": { +        "attributes": "屬性", +        "latest-telemetry": "最新遙測", +        "attributes-scope": "設備屬性範圍", +        "scope-latest-telemetry": "最新遙測", +        "scope-client": "客戶端屬性", +        "scope-server": "服務端屬性", +        "scope-shared": "共享屬性", +        "add": "增加屬性", +        "key": "鍵", +        "last-update-time": "最後更新時間", +        "key-required": "屬性鍵必填。", +        "value": "值", +        "value-required": "屬性值必填。", +        "delete-attributes-title": "您確定要刪除 { count, plural, 1 {1 屬性} other {# 屬性} }嗎?", +        "delete-attributes-text": "注意,確認後所有選中的屬性都會被刪除。", + "delete-attributes": "刪除屬性", +        "enter-attribute-value": "輸入屬性值", +        "show-on-widget": "在部件上顯示", +        "widget-mode": "部件模式", +        "next-widget": "下一個部件", +        "prev-widget": "上一個部件", +        "add-to-dashboard": "增加到儀表板", +        "add-widget-to-dashboard": "將部件增加到儀表板", +        "selected-attributes": "{ count, plural, 1 {1 屬性} other {# 屬性} } 被選中", +        "selected-telemetry": "{ count, plural, 1 {1 遙測} other {# 遙測} } 被選中" +    }, +    "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": "讀取屬性", +        "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": "地址", +        "address2": "地址2", +        "phone": "手機", +        "email": "郵箱", +        "no-address": "無地址" +    }, +    "common": { +        "username": "用戶名", +        "password": "密碼", +        "enter-username": "輸入用戶名", +        "enter-password": "輸入密碼", +        "enter-search": "輸入檢索條件" +    }, +    "content-type": { +        "json": "Json", +        "text": "Text", +        "binary": "Binary (Base64)" +    }, +    "customer": { +        "customer": "客戶", +        "customers": "客戶", +        "management": "客戶管理", +        "dashboard": "客戶儀表板", +        "dashboards": "客戶儀表板", +        "devices": "客戶設備", +        "entity-views": "客戶實體視圖", +        "assets": "客戶資產", +        "public-dashboards": "公共儀表板", +        "public-devices": "公共設備", +        "public-assets": "公共資產", +        "public-entity-views": "公共實體視圖", +        "add": "增加客戶", +        "delete": "刪除客戶", +        "manage-customer-users": "管理客戶用戶", +        "manage-customer-devices": "管理客戶設備", +        "manage-customer-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 客戶} other {# 客戶} }嗎?", +        "delete-customers-action-title": "刪除 { count, plural, 1 {1 客戶} other {# 客戶} }", +        "delete-customers-text": "小心!確認後,所有選擇的客戶將被刪除,所有相關資料將無法恢復。", +        "manage-users": "管理用戶", +        "manage-assets": "管理資產", +        "manage-devices": "管理設備", +        "manage-dashboards": "管理儀表板", +        "title": "標題", +        "title-required": "需要標題", +        "description": "描述", +        "details": "詳細資訊", +        "events": "事件", +        "copyId": "複製客戶ID", +        "idCopiedMessage": "客戶ID已複製到剪貼板", +        "select-customer": "選擇客戶", +        "no-customers-matching": "沒有找到符合 '{{entity}}' 的客戶。", +        "customer-required": "客戶是必選項", +        "select-default-customer": "選擇預設的客戶", +        "default-customer": "預設客戶", +        "default-customer-required": "為了測試租戶級別上的儀表板,需要預設客戶。" +    }, + "datetime": { + "date-from": "日期從", + "time-from": "時間從", + "date-to": "日期到", + "time-to": "時間到" + }, + "dashboard": { +        "dashboard": "儀表板", +        "dashboards": "儀表板庫", +        "management": "儀表板管理", +        "view-dashboards": "查看儀表板", +        "add": "增加儀表板", +        "assign-dashboard-to-customer": "將儀表板分配給客戶", +        "assign-dashboard-to-customer-text": "請選擇要分配給客戶的儀表板", +        "assign-to-customer-text": "請選擇客戶分配儀表板", +        "assign-to-customer": "分配給客戶", +        "unassign-from-customer": "取消分配客戶", +        "make-public": "儀表板設為公開", +        "make-private": "儀表板設為私有", +        "manage-assigned-customers": "管理已分配的客戶", +        "assigned-customers": "已分配的客戶", +        "assign-to-customers": "將儀表板分配給客戶", +        "assign-to-customers-text": "請選擇客戶指定儀表板", +        "unassign-from-customers": "客戶未分配儀表板", +        "unassign-from-customers-text": "請選擇從儀表板中取消分配的客戶", +        "no-dashboards-text": "沒有找到儀表板", +        "no-widgets": "沒有配置部件", +        "add-widget": "增加新的部件", +        "title": "標題", +        "select-widget-title": "選擇部件", +        "select-widget-subtitle": "可用的部件類型列表", +        "delete": "刪除儀表板", +        "title-required": "需要標題。", +        "description": "描述", +        "details": "詳細資訊", +        "dashboard-details": "儀表板詳細資訊", +        "add-dashboard-text": "增加新的儀表板", +        "assign-dashboards": "分配儀表板", +        "assign-new-dashboard": "分配新的儀表板", +        "assign-dashboards-text": "分配 { count, plural, 1 {1 儀表板} other {# 儀表板} } 給客戶", +        "unassign-dashboards-action-text": "未分配 { count, plural, 1 {1 儀表板} other {# 儀表板} } 給客戶", +        "delete-dashboards": "刪除儀表板", +        "unassign-dashboards": "取消分配儀表板", + "unassign-dashboards-action-title": "從客戶處取消分配 { count, plural, 1 {1 儀表板} other {# 儀表板} } ", +        "delete-dashboard-title": "您確定要刪除儀表板 '{{dashboardTitle}}'嗎?", +        "delete-dashboard-text": "小心!確認後儀表板及其所有相關資料將無法恢復。", +        "delete-dashboards-title": "你確定你要刪除 { count, plural, 1 {1 儀表板} other {# 儀表板} }嗎?", +        "delete-dashboards-action-title": "刪除 { count, plural, 1 {1 儀表板} other {# 儀表板} }", +        "delete-dashboards-text": "小心!確認後所有選擇的儀表板將被刪除,所有相關資料將無法恢復。", +        "unassign-dashboard-title": "您確定要取消分配儀表板 '{{dashboardTitle}}'嗎?", +        "unassign-dashboard-text": "確認後,面板將被取消分配,客戶將無法存取。", +        "unassign-dashboard": "取消分配儀表板", +        "unassign-dashboards-title": "您確定要取消分配儀表板 { count, plural, 1 {1 儀表板} other {# 儀表板} } 嗎?", +        "unassign-dashboards-text": "確認後,所有選擇的儀表板將被取消分配,客戶將無法存取。", +        "public-dashboard-title": "儀表板現已公佈", +        "public-dashboard-text": "你的儀表板{{dashboardTitle}} 已被公開,可通過如下連結存取:", +        "public-dashboard-notice": "提示: 不要忘記將相關設備公開以存取其資料。", +        "make-private-dashboard-title": "您確定要將儀表板 '{{dashboardTitle}}' 設為私有嗎?", +        "make-private-dashboard-text": "確認後,儀表板將被設為私有,不能被其他人存取。", +        "make-private-dashboard": "儀表板設為私有", +        "socialshare-text": "'{{dashboardTitle}}' 由Thingsboard提供支持", +        "socialshare-title": "'{{dashboardTitle}}' 由Thingsboard提供支持", +        "select-dashboard": "選擇儀表板", +        "no-dashboards-matching": "找不到符合 '{{entity}}' 的儀表板。", +        "dashboard-required": "儀表板必填。", +        "select-existing": "選擇現有儀表板", +        "create-new": "建立新的儀表板", +        "new-dashboard-title": "新儀表板標題", +        "open-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": "部件間邊距", +        "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-row-height": "移動端行高距(px)", +        "mobile-row-height-required": "移動端行高距必填。", +        "min-mobile-row-height-message": "移動端行高距至少5px。", +        "max-mobile-row-height-message": "移動端行高距最多200px。", +        "display-title": "顯示儀表板標題", +        "toolbar-always-open": "工具欄常駐", +        "title-color": "標題顏色", +        "display-dashboards-selection": "顯示儀表板選項", +        "display-entities-selection": "顯示實體選項", +        "display-dashboard-timewindow": "顯示時間窗口", +        "display-dashboard-export": "顯示匯出", +        "import": "匯入儀表板", +        "export": "匯出儀表板", +        "export-failed-error": "無法匯出儀表板: {{error}}", +        "create-new-dashboard": "建立新的儀表板", +        "dashboard-file": "儀表板文件", +        "invalid-dashboard-file-error": "無法匯入儀表板: 儀表板資料結構無效。", +        "dashboard-import-missing-aliases-title": "配置匯入儀表板使用的別名", +        "create-new-widget": "建立新部件", +        "import-widget": "匯入部件", +        "widget-file": "部件文件", + "invalid-widget-file-error": "無法匯入窗口部件: 窗口部件資料結構無效。", +        "widget-import-missing-aliases-title": "配置匯入的窗口部件使用的別名", +        "open-toolbar": "打開儀表板工具欄", +        "close-toolbar": "關閉工具欄", +        "configuration-error": "配置錯誤", +        "alias-resolution-error-title": "儀表板別名配置錯誤", +        "invalid-aliases-config": "無法找到與某些別名過濾器符合的任何設備。
請聯繫您的管理員以解決此問題。", +        "select-devices": "選擇設備", +        "assignedToCustomer": "分配給客戶", +        "public": "公共", +        "public-link": "公共連結", +        "copy-public-link": "複製公共連結", +        "public-link-copied-message": "儀表板的公共連結已被複製到剪貼板", +        "manage-states": "儀表板狀態管理", +        "states": "儀表板狀態", +        "search-states": "儀表板狀態檢索", +        "selected-states": "{ count, plural, 1 {1 儀表板狀態} other {# 儀表板狀態} } 選中", +        "edit-state": "儀表板狀態編輯", +        "delete-state": "刪除儀表板狀態", +        "add-state": "增加儀表板狀態", +        "state": "儀表板狀態", +        "state-name": "狀態名", +        "state-name-required": "儀表板狀態名必填。", +        "state-id": "狀態ID", +        "state-id-required": "儀表板狀態ID必填。", +        "state-id-exists": "儀表板狀態ID已經存在。", +        "is-root-state": "根狀態", +        "delete-state-title": "刪除儀表板狀態", +        "delete-state-text": "確定要刪除儀表板狀態 '{{stateName}}' 嗎?", +        "show-details": "顯示詳細資訊", +        "hide-details": "隱藏詳細資訊", +        "select-state": "選擇目標狀態", +        "state-controller": "狀態控制" + }, + "datakey": { +        "settings": "設定", +        "advanced": "進階", +        "label": "標籤", +        "color": "顏色", +        "units": "單位符號", +        "decimals": "小數位數", +        "data-generation-func": "資料生成功能", +        "use-data-post-processing-func": "使用資料後處理功能", +        "configuration": "資料鍵配置", +        "timeseries": "時間序列", +        "attributes": "屬性", +        "alarm": "報警字段", +        "timeseries-required": "需要設備時間序列。", +        "timeseries-or-attributes-required": "設備時間/屬性必填。", +        "maximum-timeseries-or-attributes": "最大允許 { count, plural, 1 {1 時間序列/屬性} other {# 時間序列/屬性} }", +        "alarm-fields-required": "警告字段必填。", +        "function-types": "函數類型", +        "function-types-required": "需要函數類型。", +        "maximum-function-types": "至少需要 { count, plural, 1 {1 函數類型} other {# 函數類型} }" +    }, + "datasource": { +        "type": "資料源類型", +        "name": "資料源名稱", +        "add-datasource-prompt": "請增加資料源" +    }, +    "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}}'。
設備別名必須是唯一的。", +        "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 設備} other {# 設備}}分配給客戶", +        "delete-devices": "刪除設備", +        "unassign-from-customer": "取消分配客戶", +        "unassign-devices": "取消分配設備", +        "unassign-devices-action-title": "從客戶處取消分配{count,plural,1 {1 設備} other {# 設備}}", +        "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 設備} other {# 設備}} 嗎?", +        "delete-devices-action-title": "刪除 {count,plural,1 {1 設備} other {# 設備}}", +        "delete-devices-text": "小心!確認後所有選擇的設備將被刪除,所有相關資料將無法恢復。", +        "unassign-device-title": "您確定要取消分配設備 '{{deviceName}}'?", +        "unassign-device-text": "確認後,設備將被取消分配,客戶將無法存取。", +        "unassign-device": "取消分配設備", +        "unassign-devices-title": "您確定要取消分配{count,plural,1 {1 設備} other {# 設備}} 嗎?", +        "unassign-devices-text": "確認後,所有選擇的設備將被取消分配,並且客戶將無法存取。", +        "device-credentials": "設備憑據", +        "credentials-type": "憑據類型", +        "access-token": "存取令牌", +        "access-token-required": "需要存取令牌", +        "access-token-invalid": "存取令牌長度必須為1到20個字符。", +        "rsa-key": "RSA公鑰", +        "rsa-key-required": "需要RSA公鑰", +        "secret": "密鑰", + "secret-required": "密鑰必填", +        "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": "說明", +        "events": "事件", +        "details": "詳細訊息", +        "copyId": "複製設備ID", +        "copyAccessToken": "複製存取令牌", +        "idCopiedMessage": "設備ID已複製到剪貼板", +        "accessTokenCopiedMessage": "設備存取令牌已複製到剪貼板", +        "assignedToCustomer": "分配給客戶", +        "unable-delete-device-alias-title": "無法刪除設備別名", +        "unable-delete-device-alias-text": "設備別名 '{{deviceAlias}}' 不能夠被刪除,因為它被下列部件所使用:
{{widgetsList}}", +        "is-gateway": "是閘道", +        "public": "公開", +        "device-public": "設備公開", +        "select-device": "選擇設備" +    }, + "dialog": { +        "close": "關閉對話框" +    }, +    "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}}' 被以下部件使用不能刪除:
{{widgetsList}}", +        "duplicate-alias-error": "別名 '{{alias}}' 重複。
同一儀表板別名必須唯一。", +        "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 {設備} other {# 設備列表} }", +        "device-name-starts-with": "以 '{{prefix}}' 開頭的設備", +        "type-asset": "資產", +        "type-assets": "資產", +        "list-of-assets": "{ count, plural, 1 {資產} other {# 資產列表} }", +        "asset-name-starts-with": "以 '{{prefix}}' 開頭的資產", +        "type-entity-view": "實體視圖", +        "type-entity-views": "實體視圖", +        "list-of-entity-views": "{ count, plural, 1 {實體視圖} other {# 實體視圖列表} }", +        "entity-view-name-starts-with": "以 '{{prefix}}' 開頭的實體視圖", +        "type-rule": "規則", +        "type-rules": "規則", +        "list-of-rules": "{ count, plural, 1 {規則} other {# 規則列表} }", +        "rule-name-starts-with": "以 '{{prefix}}' 開頭的規則", +        "type-plugin": "插件", +        "type-plugins": "插件", +        "list-of-plugins": "{ count, plural, 1 {插件} other {# 插件列表} }", +        "plugin-name-starts-with": "以 '{{prefix}}' 開頭的插件", +        "type-tenant": "租戶", +        "type-tenants": "租戶", +        "list-of-tenants": "{ count, plural, 1 {租戶} other {# 租戶列表} }", +        "tenant-name-starts-with": "以 '{{prefix}}' 開頭的租戶", +        "type-customer": "客戶", +        "type-customers": "客戶", + "list-of-customers": "{ count, plural, 1 {客戶} other {# 客戶列表} }", +        "customer-name-starts-with": "以 '{{prefix}}' 開頭的客戶", +        "type-user": "用戶", +        "type-users": "用戶", +        "list-of-users": "{ count, plural, 1 {用戶} other {# 用戶列表} }", +        "user-name-starts-with": "以 '{{prefix}}' 開頭的用戶", +        "type-dashboard": "儀表板", +        "type-dashboards": "儀表板", +        "list-of-dashboards": "{ count, plural, 1 {儀表板} other {# 儀表板列表} }", +        "dashboard-name-starts-with": "以 '{{prefix}}' 開頭的儀表板", +        "type-alarm": "警告", +        "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 {# 規則鏈列表} }", +        "rulechain-name-starts-with": "規則鏈前綴名稱 '{{prefix}}'", +        "type-current-customer": "當前客戶", +        "search": "實體檢索", +        "selected-entities": "{ count, plural, 1 {1 實體} other {# 實體} } 被選中", +        "entity-name": "實體名", +        "details": "實體詳細資訊", +        "no-entities-prompt": "沒有找到實體", +        "no-data": "無資料" +    }, + "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}}'。
實體視圖別名必須是唯一的。", +        "configure-alias": "配置 '{{alias}}' 別名", +        "no-devices-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 實體視圖} 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": "B小心,確認後,所有選擇的實體視圖將被刪除,所有相關的資料將變得無法恢復。", +        "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", + "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": "結束日期", +        "end-ts": "結束時間", +        "date-limits": "日期限製", +        "client-attributes": "客戶端屬性", +        "shared-attributes": "共享屬性", +        "server-attributes": "服務端屬性", +        "timeseries": "時間序列", +        "client-attributes-placeholder": "客戶端屬性", +        "shared-attributes-placeholder": "共享屬性", +        "server-attributes-placeholder": "服務端屬性", +        "timeseries-placeholder": "時間序列", +        "target-entity": "目標實體", +        "attributes-propagation": "屬性傳播", +        "attributes-propagation-hint": "每次儲存或更新這個實體視圖時,實體視圖將自動從目標實體複製指定的屬性。由於性能原因,目標實體屬性不會在每次屬性更改時傳播到實體視圖。您可以通過配置\"copy to view\"規則鏈中的規則節點,並將\"Post attributes\"和\"attributes Updated\"消息連結到新規則節點,從而啟用自動傳播。", +        "timeseries-data": "時間序列資料", +        "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": "事件類型", +        "type-error": "錯誤", +        "type-lc-event": "生命週期事件", +        "type-stats": "類型統計", +        "type-debug-rule-node": "測試", +        "type-debug-rule-chain": "測試", +        "no-events-prompt": "找不到事件", +        "error": "錯誤", +        "alarm": "報警", +        "event-time": "事件時間", +        "server": "伺服器", +        "body": "整體", +        "method": "方法", +        "type": "類型", +        "entity": "實體", +        "message-id": "消息ID", +        "message-type": "消息類型", +        "data-type": "資料類型", +        "relation-type": "關係類型", +        "metadata": "元資料", +        "data": "資料", +        "event": "事件", +        "status": "狀態", +        "success": "成功", +        "failed": "失敗", +        "messages-processed": "消息處理", +        "errors-occurred": "錯誤發生" +    }, + "extension": { +        "extensions": "擴展", +        "selected-extensions": "{ count, plural, 1 {1 擴展} other {# 擴展} } 被選擇", +        "type": "類型", +        "key": "鍵名", +        "value": "值", +        "id": "ID", +        "extension-id": "擴展ID", +        "extension-type": "擴展類型", +        "transformer-json": "JSON *", +        "unique-id-required": "當前擴展ID已經存在。", +        "delete": "刪除擴展", +        "add": "增加擴展", +        "edit": "編輯擴展", +        "delete-extension-title": "確實要刪除擴展名'{{extensionId}}'嗎?", +        "delete-extension-text": "小心,確認後,擴展和所有相關資料將變得無法恢復。", +        "delete-extensions-title": "您確定要刪除 { count, plural, 1 {1 表達式} other {# 表達式} }嗎?", +        "delete-extensions-text": "小心,確認後,所有選擇的擴展將被刪除。", +        "converters": "轉換器", +        "converter-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": "代理伺服器組", +        "add-broker": "增加代理伺服器", +        "host": "主機", +        "port": "連接埠", +        "port-range": "連接埠應該在1到65535的範圍內。", +        "ssl": "Ssl", +        "credentials": "證書", +        "username": "用戶名", +        "password": "密碼", +        "retry-interval": "以毫秒為單位的重試間隔", +        "anonymous": "匿名", +        "basic": "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": "服務端RPC", +        "add-server-side-rpc-request": "增加服務端RPC請求", +        "device-name-filter": "設備名稱濾波", +        "attribute-filter": "屬性濾波", +        "method-filter": "方法濾波", +        "request-topic-expression": "請求主題表達式", +        "response-timeout": "毫秒內響應超時", +        "topic-expression": "主題表達", +        "client-scope": "客戶範圍", +        "add-device": "增加伺服器", +        "opc-server": "伺服器組", +        "opc-add-server": "增加伺服器", +        "opc-add-server-prompt": "請增加伺服器", +        "opc-application-name": "應用名稱", +        "opc-application-uri": "應用URI", +        "opc-scan-period-in-seconds": "秒級掃描週期", +        "opc-security": "安全性", +        "opc-identity": "身份", +        "opc-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-port-name": "串口名稱", +        "modbus-encoding": "編碼", +        "modbus-parity": "奇偶性", +        "modbus-baudrate": "鮑率", +        "modbus-databits": "資料位", +        "modbus-stopbits": "停止位", + "modbus-databits-range": "資料位應該在7到8的範圍內。", +        "modbus-stopbits-range": "停止位應該在1到2的範圍內。", +        "modbus-unit-id": "單位編號", +        "modbus-unit-id-range": "單位ID應該在1到247的範圍內", +        "modbus-device-name": "設備名稱", +        "modbus-poll-period": "輪詢週期 (ms)", +        "modbus-attributes-poll-period": "輪詢屬性週期 (ms)", +        "modbus-timeseries-poll-period": "時間序列輪詢週期 (ms)", +        "modbus-poll-period-range": "輪詢週期應為正值。", +        "modbus-tag": "標籤", +        "modbus-function": "函數", +        "modbus-register-address": "寄存器地址", +        "modbus-register-address-range": "寄存器地址應該在0到65535的範圍內。", +        "modbus-register-bit-index": "位索引", +        "modbus-register-bit-index-range": "位索引應該在0到15的範圍內。", +        "modbus-register-count": "寄存器計數", +        "modbus-register-count-range": "寄存器計數應該是一個正值。", +        "modbus-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 項} other {# 項} }嗎?", +        "delete-items-action-title": "刪除 { count, plural, 1 {1 項} other {# 項} }", +        "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": "頭像", +        "open-user-menu": "打開用戶菜單" +    }, +    "import": { +        "no-file": "沒有選擇文件", +        "drop-file": "拖動一個JSON文件或者單擊以選擇要上傳的文件。" +    }, +    "item": { +        "selected": "選擇" +    }, +    "js-func": { +        "no-return-error": "函數必須返回值!", +        "return-type-mismatch": "函數必須返回 '{{type}}' 類型的值!", +        "tidy": "整理" +    }, + "key-val": { +        "key": "鍵名", +        "value": "值", +        "remove-entry": "刪除條目", +        "add-entry": "增加條目", +        "no-data": "沒有條目" +    }, +    "layout": { +        "layout": "佈局", +        "manage": "佈局管理", +        "settings": "佈局設定", +        "color": "顏色", +        "main": "主體", +        "right": "右側", +        "select": "選擇目標佈局" +    }, +    "legend": { +        "position": "圖例位置", +        "show-max": "顯示最大值", +        "show-min": "顯示最小值", +        "show-avg": "顯示平均值", +        "show-total": "顯示總數", +        "settings": "圖例設定", +        "min": "最小值", +        "max": "最大值", +        "avg": "平均值", +        "total": "總數" +    }, +    "login": { +        "login": "登入", +        "request-password-reset": "請求密碼重置", +        "reset-password": "重置密碼", +        "create-password": "建立密碼", +        "passwords-mismatch-error": "輸入的密碼必須相同!", +        "password-again": "再次輸入密碼", +        "sign-in": "登入 ", +        "username": "用戶名(電子郵件)", +        "remember-me": "記住我", +        "forgot-password": "忘記密碼?", +        "password-reset": "密碼重置", +        "new-password": "新密碼", +        "new-password-again": "再次輸入新密碼", +        "password-link-sent-message": "密碼重置連結已成功發送!", +        "email": "電子郵件" +    }, + "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 關聯} other {# 關聯} } 被選中", +        "type": "類型", +        "to-entity-type": "到實體類型", +        "to-entity-name": "到實體名稱", +        "from-entity-type": "從實體類型", +        "from-entity-name": "從實體類型", +        "to-entity": "到實體", +        "from-entity": "從實體", +        "delete": "刪除關聯", +        "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 關聯} other {# 關聯} }?", +        "delete-to-relations-text": "確定刪除所有選擇的關聯關係後,與當前實體對應的所有關聯關係將被移除。", +        "delete-from-relation-title": "確定要從實體 '{{entityName}}' 刪除關聯嗎?", +        "delete-from-relation-text": "確定刪除後,當前實體將與實體 '{{entityName}}' 取消關聯", +        "delete-from-relations-title": "確定刪除 { count, plural, 1 {1 關聯} other {# 關聯} } 嗎?", +        "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": "刪除規則", +        "activate": "啟動規則", +        "suspend": "暫停規則", +        "active": "啟動", +        "suspended": "暫停", +        "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 規則鏈}其他{# 規則鏈庫}}嗎?", +        "delete-rulechains-action-title": "刪除 { count, plural, 1 {1 規則鏈} other {# 規則鏈庫} }", +        "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": "複製規則鏈ID", +        "idCopiedMessage": "規則ID已經複製到剪貼板", +        "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": "刪除選擇", +        "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": "自定義連結標籤必填", +        "type-filter": "濾波器", +        "type-filter-details": "使用配置條件過濾傳入消息", +        "type-enrichment": "屬性集", +        "type-enrichment-details": "向消息元資料中增加附加訊息", +        "type-transformation": "變換", +        "type-transformation-details": "更改消息有效載荷和元資料", +        "type-action": "動作", +        "type-action-details": "執行特別動作", +        "type-external": "外部的", +        "type-external-details": "與外部系統交互", +        "type-rule-chain": "規則鏈", +        "type-rule-chain-details": "將傳入消息轉發到指定的規則鏈", +        "type-in​​put": "輸入", +        "type-in​​put-details": "規則鏈的邏輯輸入,將傳入消息轉發到下一個相關規則節點", +        "directive-is-not-loaded": "定義的配置指令 '{{directiveName}}' 不可用。", +        "ui-resources-load-error": "加載配置UI資源失敗。", +        "invalid-target-rulechain": "無法解析目標規則鏈!", +        "test-script-function": "測試腳本功能", +        "message": "消息", +        "message-type": "消息類型", +        "message-type-required": "消息類型必填", +        "metadata": "元資料", +        "metadata-required": "元資料項不能為空。", +        "output": "輸出", +        "test": "測試", +        "help": "幫助" +    }, +    "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 租戶} other {# 租戶}} 嗎?", +        "delete-tenants-action-title": "刪除 { count, plural, 1 {1 租戶} other {# 租戶} }", +        "delete-tenants-text": "小心!確認後,所有選擇的租戶將被刪除,所有相關資料將無法恢復。", +        "title": "標題", +        "title-required": "標題必填。", +        "description": "描述", +        "details": "詳細資訊", +        "events": "事件", +        "copyId": "複製租戶ID", +        "idCopiedMessage": "租戶ID已經複製到剪貼板", +        "select-tenant": "選擇租戶", +        "no-tenants-matching": "沒有找到符合 '{{entity}}' 的租戶", +        "tenant-required": "租戶必填" +    }, +    "timeinterval": { +        "seconds-interval": "{ seconds, plural, 1 {1 秒} other {# 秒} }", +        "minutes-interval": "{ minutes, plural, 1 {1 分} other {# 分} }", +        "hours-interval": "{ hours, plural, 1 {1 小時} other {# 小時} }", +        "days-interval": "{ days, plural, 1 {1 天} other {# 天} }", +        "days": "天", +        "hours": "時", +        "minutes": "分", +        "seconds": "秒", +        "advanced": "高級" +    }, + "timewindow": { +        "days": "{ days, plural, 1 { 天 } other {# 天 } }", +        "hours": "{ hours, plural, 0 { 小時 } 1 {1 小時 } other {# 小時 } }", +        "minutes": "{ minutes, plural, 0 { 分 } 1 {1 分 } other {# 分 } }", +        "seconds": "{ seconds, plural, 0 { 秒 } 1 {1 秒 } other {# 秒 } }", +        "realtime": "實時", +        "history": "歷史", +        "last-prefix": "最後", +        "period": "從 {{ startTime }} 到 {{ endTime }}", +        "edit": "編輯時間窗口", +        "date-range": "日期範圍", +        "last": "最後", +        "time-period": "時間段" +    }, +    "user": { +        "user": "用戶", +        "users": "用戶", +        "customer-users": "客戶用戶", +        "tenant-admins": "租戶管理員", +        "sys-admin": "系統管理員", +        "tenant-admin": "租戶管理員", +        "customer": "客戶", +        "anonymous": "匿名", +        "add": "增加用戶", +        "delete": "刪除用戶", +        "add-user-text": "增加新用戶", +        "no-users-text": "找不到用戶", +        "user-details": "用戶詳細訊息", +        "delete-user-title": "您確定要刪除用戶 '{{userEmail}}' 嗎?", +        "delete-user-text": "小心!確認後,用戶和所有相關資料將無法恢復。", +        "delete-users-title": "你確定你要刪除 { count, plural, 1 {1 用戶} other {# 用戶} } 嗎?", +        "delete-users-action-title": "刪除 { count, plural, 1 {1 用戶} other {# 用戶} }", +        "delete-users-text": "小心!確認後,所有選擇的用戶將被刪除,所有相關資料將無法恢復。", +        "activation-email-sent-message": "啟動電子郵件已成功發送!", +        "resend-activation": "重新發送啟動", +        "email": "電子郵件", +        "email-required": "電子郵件必填。", +        "invalid-email-format": "無效的郵件格式。", +        "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": "發送啟動郵件", +        "activation-link": "用戶啟動連結", +        "activation-link-text": "使用該連結 啟動 啟動用戶:", +        "copy-activation-link": "複製用戶啟動連結", +        "activation-link-copied-message": "用戶啟動連結已經複製到剪貼板", +        "details": "詳細訊息" +    }, +    "value": { +        "type": "值類型", +        "string": "字符串", +        "string-value": "字符串值", +        "integer": "數字", +        "integer-value": "數字值", +        "invalid-integer-value": "整數值無效", +        "double": "雙精度浮點數", +        "double-value": "雙精度浮點數值", +        "boolean": "布林", +        "boolean-value": "布林值", +        "false": "假", +        "true": "真", +        "long": "Long" +    }, +    "widget": { +        "widget-library": "部件庫", +        "widget-bundle": "部件包", +        "select-widgets-bundle": "選擇部件包", +        "management": "管理部件", +        "editor": "部件編輯器", +        "widget-type-not-found": "加載部件配置出錯。
可能關聯的\n 部件已經刪除了。", +        "widget-type-load-error": "由於以下錯誤未加載小部件:", +        "remove": "刪除部件", +        "edit": "編輯部件", +        "remove-widget-title": "確實要刪除 '{{widgetTitle}}'部件嗎?", +        "remove-widget-text": "確認後,控件和所有相關資料將變得無法恢復。", +        "timeseries": "時間序列", +        "search-data": "搜尋資料", +        "no-data-found": "沒有找到資料", +        "latest-values": "最新值", +        "rpc": "控件部件", +        "alarm": "警告部件", +        "static": "靜態部件", +        "select-widget-type": "選擇窗口部件類型", +        "missing-widget-title-error": "部件標題必須指定!", +        "widget-saved": "部件已儲存", +        "unable-to-save-widget-error": "無法儲存部件!控件有錯誤!", +        "save": "儲存部件", +        "saveAs": "部件另存為", +        "save-widget-type-as": "部件類型另存為", +        "save-widget-type-as-text": "請輸入新的部件標題或選擇目標部件包", +        "toggle-fullscreen": "切換全螢幕", +        "run": "執行部件", +        "title": "部件標題", +        "title-required": "需要部件標題。", +        "type": "部件類型", +        "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": "您確定要刪除部件類型 '{{widgetName}}'嗎?", +        "remove-widget-type-text": "確認後,窗口部件類型和所有相關資料將無法恢復。", +        "remove-widget-type": "刪除部件類型", +        "add-widget-type": "增加新的部件類型", +        "widget-type-load-failed-error": "無法加載部件類型!", +        "widget-template-load-failed-error": "無法加載部件模板!", +        "add": "增加部件", +        "undo": "復原部件更改", +        "export": "匯出部件" +    }, + "widget-action": { +        "header-button": "部件頂部按鈕", +        "open-dashboard-state": "切換到新儀表板狀態", +        "update-dashboard-state": "更新當前儀表板狀態", +        "open-dashboard": "切換到另一個儀表板", +        "custom": "自定義動作", +        "target-dashboard-state": "目標儀表板狀態", +        "target-dashboard-state-required": "目標儀表板狀態必填", +        "set-entity-from-widget": "從部件中設定實體", +        "target-dashboard": "目標儀表板", +        "open-right-layout": "打開右側佈局 (移動端視圖)" +    }, +    "widgets-bundle": { +        "current": "當前包", +        "widgets-bundles": "部件包", +        "add": "增加部件包", +        "delete": "刪除部件包", +        "title": "標題", +        "title-required": "標題必填。", +        "add-widgets-bundle-text": "增加新的部件包", +        "no-widgets-bundles-text": "找不到部件包", +        "empty": "部件包是空的", +        "details": "詳細資訊", +        "widgets-bundle-details": "部件包詳細訊息", +        "delete-widgets-bundle-title": "您確定要刪除部件包 '{{widgetsBundleTitle}}'嗎?", +        "delete-widgets-bundle-text": "小心!確認後,部件包和所有相關資料將無法恢復。", +        "delete-widgets-bundles-title": "你確定你要刪除 { count, plural, 1 {1 部件包} other {# 部件包} } 嗎?", +        "delete-widgets-bundles-action-title": "刪除 { count, plural, 1 {1 部件包} other {# 部件包} }", +        "delete-widgets-bundles-text": "小心!確認後,所有選擇的部件包將被刪除,所有相關資料將無法恢復。", +        "no-widgets-bundles-matching": "沒有找到與 '{{widgetsBundle}}' 符合的部件包。", +        "widgets-bundle-required": "需要部件包。", +        "system": "系統", +        "import": "匯入部件包", +        "export": "匯出部件包", +        "export-failed-error": "無法匯出部件包: {{error}}", +        "create-new-widgets-bundle": "建立新的部件包", +        "widgets-bundle-file": "部件包文件", +        "invalid-widgets-bundle-file-error": "無法匯入部件包:無效的部件包資料結構。" +    }, +    "widget-config": { +        "data": "資料", +        "settings": "設定", +        "advanced": "高級", +        "title": "標題", +        "general-settings": "一般設定", +        "display-title": "顯示標題", +        "drop-shadow": "陰影", +        "enable-fullscreen": "啟用全螢幕", +        "background-color": "背景顏色", +        "text-color": "文字顏色", +        "padding": "填充", +        "margin": "邊緣", +        "widget-style": "部件風格", +        "title-style": "標題風格", +        "mobile-mode-settings": "移動端設定", +        "order": "順序", +        "height": "高度", +        "units": "特殊符號展示值", +        "decimals": "浮點數後的位數", +        "timewindow": "時間窗口", +        "use-dashboard-timewindow": "使用儀表板的時間窗口", +        "display-legend": "顯示圖例", +        "datasources": "資料源", +        "maximum-datasources": "最大允許 { count, plural, 1 {1 資料} other {# 資料} }", +        "datasource-type": "類型", +        "datasource-parameters": "參數", +        "remove-datasource": "移除資料源", +        "add-datasource": "增加資料源", +        "target-device": "目標設備", +        "alarm-source": "警告源", +        "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": "刪除部件動作", +        "delete-action-text": "確定要刪除部件動作 '{{actionName}}' 嗎?" +    }, +    "widget-type": { +        "import": "匯入部件類型", +        "export": "匯出部件類型", +        "export-failed-error": "無法匯出部件類型: {{error}}", +        "create-new-widget-type": "建立新的部件類型", +        "widget-type-file": "部件類型文件", +        "invalid-widget-type-file-error": "無法匯入部件類型:無效的部件類型資料結構。" +    }, + "widgets": { +        "date-range-navigator": { +            "localizationMap": { +                "Sun": "週日", +                "Mon": "週一", +                "Tue": "週二", +                "Wed": "週三", +                "Thu": "週四", +                "Fri": "週五", +                "Sat": "週六", +                "Jan": "1月", +                "Feb": "2月", +                "Mar": "3月", +                "Apr": "4月", +                "May": "5月", +                "Jun": "6月", +                "Jul": "7月", +                "Aug": "8月", +                "Sep": "9月", +                "Oct": "10月", +                "Nov": "11月", +                "Dec": "12月", +                "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": "顯示所有圖示" +    }, +    "custom": { +        "widget-action": { +            "action-cell-button": "動作單元格按鈕", +            "row-click": "點選行", +            "marker-click": "點選標記", +            "polygon-click": "單擊多邊形", +            "tooltip-tag-action": "提示標籤動作" +        } +    }, +    "language": { +        "language": "語言", +        "locales": { +            "de_DE": "德文", +            "en_US": "英文", +            "fr_FR": "法文", +            "ko_KR": "韓文", +            "zh_CN": "簡體中文", +            "zh_TW": "繁體中文", +            "ru_RU": "俄文", +            "es_ES": "西班牙文", +            "it_IT": "意大利文", +            "ja_JA": "日文", +            "tr_TR": "土耳其文", +            "fa_IR": "波斯文", +            "uk_UA": "烏克蘭文", +            "cs_CZ": "捷克文" +        } +    } +} From 3ba310dff0b15ef5fde824f7a7fd82ed51014801 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 6 Jan 2020 16:39:51 +0200 Subject: [PATCH 162/261] Fix didn't disable timewindow button (#2312) * Add support import label * Fix didn't disable timewindow button --- ui/src/app/components/timewindow.directive.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js index 991a3838ff..db028535a0 100644 --- a/ui/src/app/components/timewindow.directive.js +++ b/ui/src/app/components/timewindow.directive.js @@ -212,7 +212,7 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM } } - scope.isTimewindowDisabled = function () { + function isTimewindowDisabled () { return scope.disabled || (!scope.isEdit && scope.model.hideInterval && scope.model.hideAggregation && scope.model.hideAggInterval); } @@ -247,10 +247,16 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM model.hideAggregation = value.hideAggregation; model.hideAggInterval = value.hideAggInterval; } - scope.timewindowDisabled = scope.isTimewindowDisabled(); + scope.timewindowDisabled = isTimewindowDisabled(); scope.updateDisplayValue(); }; + scope.$watchGroup(['disabled', 'isEdit'], function(newValue, oldValue) { + if (!angular.equals(newValue, oldValue)) { + scope.timewindowDisabled = isTimewindowDisabled(); + } + }); + $compile(element.contents())(scope); } From 9b42397c4a820688daf07117cb7efefa3f387f46 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 6 Jan 2020 16:41:04 +0200 Subject: [PATCH 163/261] Update License header year to 2020 --- application/build.gradle | 2 +- application/pom.xml | 2 +- application/src/main/assembly/windows.xml | 2 +- application/src/main/conf/logback.xml | 2 +- application/src/main/conf/thingsboard.conf | 2 +- application/src/main/data/upgrade/1.3.0/schema_update.cql | 2 +- application/src/main/data/upgrade/1.3.1/schema_update.sql | 2 +- application/src/main/data/upgrade/1.4.0/schema_update.cql | 2 +- application/src/main/data/upgrade/1.4.0/schema_update.sql | 2 +- application/src/main/data/upgrade/2.0.0/schema_update.cql | 2 +- application/src/main/data/upgrade/2.0.0/schema_update.sql | 2 +- application/src/main/data/upgrade/2.1.1/schema_update.cql | 2 +- application/src/main/data/upgrade/2.1.1/schema_update.sql | 2 +- application/src/main/data/upgrade/2.1.2/schema_update.cql | 2 +- application/src/main/data/upgrade/2.1.2/schema_update.sql | 2 +- application/src/main/data/upgrade/2.2.0/schema_update.sql | 2 +- application/src/main/data/upgrade/2.3.1/schema_update.sql | 2 +- application/src/main/data/upgrade/2.4.0/schema_update.sql | 2 +- application/src/main/data/upgrade/2.4.2/schema_update.sql | 2 +- .../org/thingsboard/server/ThingsboardInstallApplication.java | 2 +- .../org/thingsboard/server/ThingsboardServerApplication.java | 2 +- .../java/org/thingsboard/server/actors/ActorSystemContext.java | 2 +- .../main/java/org/thingsboard/server/actors/app/AppActor.java | 2 +- .../java/org/thingsboard/server/actors/app/AppInitMsg.java | 2 +- .../java/org/thingsboard/server/actors/device/DeviceActor.java | 2 +- .../thingsboard/server/actors/device/DeviceActorCreator.java | 2 +- .../server/actors/device/DeviceActorMessageProcessor.java | 2 +- .../server/actors/device/DeviceActorToRuleEngineMsg.java | 2 +- .../java/org/thingsboard/server/actors/device/SessionInfo.java | 2 +- .../thingsboard/server/actors/device/SessionInfoMetaData.java | 2 +- .../server/actors/device/SessionTimeoutCheckMsg.java | 2 +- .../server/actors/device/ToDeviceRpcRequestMetadata.java | 2 +- .../server/actors/device/ToServerRpcRequestMetadata.java | 2 +- .../thingsboard/server/actors/rpc/BasicRpcSessionListener.java | 2 +- .../org/thingsboard/server/actors/rpc/RpcBroadcastMsg.java | 2 +- .../org/thingsboard/server/actors/rpc/RpcManagerActor.java | 2 +- .../org/thingsboard/server/actors/rpc/RpcSessionActor.java | 2 +- .../org/thingsboard/server/actors/rpc/RpcSessionClosedMsg.java | 2 +- .../thingsboard/server/actors/rpc/RpcSessionConnectedMsg.java | 2 +- .../server/actors/rpc/RpcSessionCreateRequestMsg.java | 2 +- .../server/actors/rpc/RpcSessionDisconnectedMsg.java | 2 +- .../org/thingsboard/server/actors/rpc/RpcSessionTellMsg.java | 2 +- .../org/thingsboard/server/actors/rpc/SessionActorInfo.java | 2 +- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- .../server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java | 2 +- .../thingsboard/server/actors/ruleChain/RuleChainActor.java | 2 +- .../actors/ruleChain/RuleChainActorMessageProcessor.java | 2 +- .../server/actors/ruleChain/RuleChainManagerActor.java | 2 +- .../server/actors/ruleChain/RuleChainToRuleChainMsg.java | 2 +- .../server/actors/ruleChain/RuleChainToRuleNodeMsg.java | 2 +- .../org/thingsboard/server/actors/ruleChain/RuleNodeActor.java | 2 +- .../server/actors/ruleChain/RuleNodeActorMessageProcessor.java | 2 +- .../org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java | 2 +- .../thingsboard/server/actors/ruleChain/RuleNodeRelation.java | 2 +- .../actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java | 2 +- .../server/actors/ruleChain/RuleNodeToSelfErrorMsg.java | 2 +- .../thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java | 2 +- .../org/thingsboard/server/actors/service/ActorService.java | 2 +- .../org/thingsboard/server/actors/service/ComponentActor.java | 2 +- .../thingsboard/server/actors/service/ContextAwareActor.java | 2 +- .../thingsboard/server/actors/service/ContextBasedCreator.java | 2 +- .../thingsboard/server/actors/service/DefaultActorService.java | 2 +- .../server/actors/shared/AbstractContextAwareMsgProcessor.java | 2 +- .../thingsboard/server/actors/shared/ActorTerminationMsg.java | 2 +- .../server/actors/shared/ComponentMsgProcessor.java | 2 +- .../thingsboard/server/actors/shared/EntityActorsManager.java | 2 +- .../server/actors/shared/rulechain/RuleChainManager.java | 2 +- .../server/actors/shared/rulechain/SystemRuleChainManager.java | 2 +- .../server/actors/shared/rulechain/TenantRuleChainManager.java | 2 +- .../java/org/thingsboard/server/actors/stats/StatsActor.java | 2 +- .../org/thingsboard/server/actors/stats/StatsPersistMsg.java | 2 +- .../org/thingsboard/server/actors/stats/StatsPersistTick.java | 2 +- .../thingsboard/server/actors/tenant/DebugTbRateLimits.java | 2 +- .../java/org/thingsboard/server/actors/tenant/TenantActor.java | 2 +- .../org/thingsboard/server/config/AuditLogLevelProperties.java | 2 +- .../main/java/org/thingsboard/server/config/JwtSettings.java | 2 +- .../java/org/thingsboard/server/config/MvcCorsProperties.java | 2 +- .../thingsboard/server/config/RateLimitProcessingFilter.java | 2 +- .../org/thingsboard/server/config/SchedulingConfiguration.java | 2 +- .../org/thingsboard/server/config/SwaggerConfiguration.java | 2 +- .../server/config/ThingsboardMessageConfiguration.java | 2 +- .../server/config/ThingsboardSecurityConfiguration.java | 2 +- .../src/main/java/org/thingsboard/server/config/WebConfig.java | 2 +- .../org/thingsboard/server/config/WebSocketConfiguration.java | 2 +- .../org/thingsboard/server/controller/AdminController.java | 2 +- .../org/thingsboard/server/controller/AlarmController.java | 2 +- .../org/thingsboard/server/controller/AssetController.java | 2 +- .../org/thingsboard/server/controller/AuditLogController.java | 2 +- .../java/org/thingsboard/server/controller/AuthController.java | 2 +- .../java/org/thingsboard/server/controller/BaseController.java | 2 +- .../server/controller/ComponentDescriptorController.java | 2 +- .../org/thingsboard/server/controller/CustomerController.java | 2 +- .../org/thingsboard/server/controller/DashboardController.java | 2 +- .../org/thingsboard/server/controller/DeviceController.java | 2 +- .../server/controller/EntityRelationController.java | 2 +- .../thingsboard/server/controller/EntityViewController.java | 2 +- .../org/thingsboard/server/controller/EventController.java | 2 +- .../thingsboard/server/controller/HttpValidationCallback.java | 2 +- .../java/org/thingsboard/server/controller/RpcController.java | 2 +- .../org/thingsboard/server/controller/RuleChainController.java | 2 +- .../java/org/thingsboard/server/controller/TbUrlConstants.java | 2 +- .../org/thingsboard/server/controller/TelemetryController.java | 2 +- .../org/thingsboard/server/controller/TenantController.java | 2 +- .../java/org/thingsboard/server/controller/UserController.java | 2 +- .../thingsboard/server/controller/WidgetTypeController.java | 2 +- .../thingsboard/server/controller/WidgetsBundleController.java | 2 +- .../server/controller/plugin/TbWebSocketHandler.java | 2 +- .../exception/ThingsboardCredentialsExpiredResponse.java | 2 +- .../thingsboard/server/exception/ThingsboardErrorResponse.java | 2 +- .../server/exception/ThingsboardErrorResponseHandler.java | 2 +- .../server/install/ThingsboardInstallConfiguration.java | 2 +- .../server/install/ThingsboardInstallException.java | 2 +- .../thingsboard/server/install/ThingsboardInstallService.java | 2 +- .../cluster/discovery/CurrentServerInstanceService.java | 2 +- .../server/service/cluster/discovery/DiscoveryService.java | 2 +- .../service/cluster/discovery/DiscoveryServiceListener.java | 2 +- .../service/cluster/discovery/DummyDiscoveryService.java | 2 +- .../server/service/cluster/discovery/ServerInstance.java | 2 +- .../service/cluster/discovery/ServerInstanceService.java | 2 +- .../server/service/cluster/discovery/ZkDiscoveryService.java | 2 +- .../server/service/cluster/routing/ClusterRoutingService.java | 2 +- .../cluster/routing/ConsistentClusterRoutingService.java | 2 +- .../server/service/cluster/routing/ConsistentHashCircle.java | 2 +- .../server/service/cluster/rpc/ClusterGrpcService.java | 2 +- .../server/service/cluster/rpc/ClusterRpcService.java | 2 +- .../thingsboard/server/service/cluster/rpc/GrpcSession.java | 2 +- .../server/service/cluster/rpc/GrpcSessionListener.java | 2 +- .../thingsboard/server/service/cluster/rpc/RpcMsgListener.java | 2 +- .../service/component/AnnotationComponentDiscoveryService.java | 2 +- .../server/service/component/ComponentDiscoveryService.java | 2 +- .../server/service/encoding/DataDecodingEncodingService.java | 2 +- .../server/service/encoding/ProtoWithFSTService.java | 2 +- .../server/service/environment/EnvironmentLogService.java | 2 +- .../service/executors/ClusterRpcCallbackExecutorService.java | 2 +- .../server/service/executors/DbCallbackExecutorService.java | 2 +- .../server/service/executors/ExternalCallExecutorService.java | 2 +- .../server/service/executors/SharedEventLoopGroupService.java | 3 +-- .../install/CassandraAbstractDatabaseSchemaService.java | 2 +- .../service/install/CassandraDatabaseUpgradeService.java | 2 +- .../service/install/CassandraEntityDatabaseSchemaService.java | 2 +- .../service/install/CassandraTsDatabaseSchemaService.java | 2 +- .../org/thingsboard/server/service/install/DatabaseHelper.java | 2 +- .../server/service/install/DatabaseSchemaService.java | 2 +- .../server/service/install/DatabaseUpgradeService.java | 2 +- .../server/service/install/DefaultSystemDataLoaderService.java | 2 +- .../server/service/install/EntityDatabaseSchemaService.java | 2 +- .../org/thingsboard/server/service/install/InstallScripts.java | 2 +- .../service/install/SqlAbstractDatabaseSchemaService.java | 2 +- .../server/service/install/SqlDatabaseUpgradeService.java | 2 +- .../server/service/install/SqlEntityDatabaseSchemaService.java | 2 +- .../service/install/SqlTimescaleDatabaseSchemaService.java | 2 +- .../server/service/install/SqlTsDatabaseSchemaService.java | 2 +- .../server/service/install/SystemDataLoaderService.java | 2 +- .../server/service/install/TsDatabaseSchemaService.java | 2 +- .../server/service/install/cql/CQLStatementsParser.java | 2 +- .../server/service/install/cql/CassandraDbHelper.java | 2 +- .../thingsboard/server/service/install/sql/SqlDbHelper.java | 2 +- .../server/service/install/update/DataUpdateService.java | 2 +- .../service/install/update/DefaultDataUpdateService.java | 2 +- .../server/service/install/update/PaginatedUpdater.java | 2 +- .../thingsboard/server/service/mail/DefaultMailService.java | 2 +- .../thingsboard/server/service/mail/MailExecutorService.java | 2 +- .../server/service/rpc/DefaultDeviceRpcService.java | 2 +- .../org/thingsboard/server/service/rpc/DeviceRpcService.java | 2 +- .../thingsboard/server/service/rpc/FromDeviceRpcResponse.java | 2 +- .../thingsboard/server/service/rpc/LocalRequestMetaData.java | 2 +- .../server/service/rpc/ToDeviceRpcRequestActorMsg.java | 2 +- .../server/service/rpc/ToServerRpcResponseActorMsg.java | 2 +- .../server/service/script/AbstractJsInvokeService.java | 2 +- .../server/service/script/AbstractNashornJsInvokeService.java | 2 +- .../thingsboard/server/service/script/JsExecutorService.java | 2 +- .../org/thingsboard/server/service/script/JsInvokeRequest.java | 2 +- .../thingsboard/server/service/script/JsInvokeResponse.java | 2 +- .../org/thingsboard/server/service/script/JsInvokeService.java | 2 +- .../org/thingsboard/server/service/script/JsScriptType.java | 2 +- .../org/thingsboard/server/service/script/JsStatCallback.java | 2 +- .../server/service/script/NashornJsInvokeService.java | 2 +- .../server/service/script/RemoteJsInvokeService.java | 2 +- .../server/service/script/RemoteJsRequestEncoder.java | 2 +- .../server/service/script/RemoteJsResponseDecoder.java | 2 +- .../server/service/script/RuleNodeJsScriptEngine.java | 2 +- .../server/service/script/RuleNodeScriptFactory.java | 2 +- .../thingsboard/server/service/security/AccessValidator.java | 2 +- .../server/service/security/ValidationCallback.java | 2 +- .../thingsboard/server/service/security/ValidationResult.java | 2 +- .../server/service/security/ValidationResultCode.java | 2 +- .../service/security/auth/AbstractJwtAuthenticationToken.java | 2 +- .../server/service/security/auth/JwtAuthenticationToken.java | 2 +- .../service/security/auth/RefreshAuthenticationToken.java | 2 +- .../service/security/auth/jwt/JwtAuthenticationProvider.java | 2 +- .../auth/jwt/JwtTokenAuthenticationProcessingFilter.java | 2 +- .../security/auth/jwt/RefreshTokenAuthenticationProvider.java | 2 +- .../security/auth/jwt/RefreshTokenProcessingFilter.java | 2 +- .../service/security/auth/jwt/RefreshTokenRepository.java | 2 +- .../server/service/security/auth/jwt/RefreshTokenRequest.java | 2 +- .../service/security/auth/jwt/SkipPathRequestMatcher.java | 2 +- .../security/auth/jwt/extractor/JwtHeaderTokenExtractor.java | 2 +- .../security/auth/jwt/extractor/JwtQueryTokenExtractor.java | 2 +- .../service/security/auth/jwt/extractor/TokenExtractor.java | 2 +- .../server/service/security/auth/rest/LoginRequest.java | 2 +- .../server/service/security/auth/rest/PublicLoginRequest.java | 2 +- .../service/security/auth/rest/RestAuthenticationDetails.java | 3 +-- .../security/auth/rest/RestAuthenticationDetailsSource.java | 3 +-- .../service/security/auth/rest/RestAuthenticationProvider.java | 2 +- .../auth/rest/RestAwareAuthenticationFailureHandler.java | 2 +- .../auth/rest/RestAwareAuthenticationSuccessHandler.java | 2 +- .../service/security/auth/rest/RestLoginProcessingFilter.java | 2 +- .../security/auth/rest/RestPublicLoginProcessingFilter.java | 2 +- .../service/security/device/DefaultDeviceAuthService.java | 2 +- .../security/exception/AuthMethodNotSupportedException.java | 2 +- .../service/security/exception/JwtExpiredTokenException.java | 2 +- .../security/exception/UserPasswordExpiredException.java | 2 +- .../server/service/security/model/SecurityUser.java | 2 +- .../server/service/security/model/UserPrincipal.java | 2 +- .../server/service/security/model/token/AccessJwtToken.java | 2 +- .../server/service/security/model/token/JwtToken.java | 2 +- .../server/service/security/model/token/JwtTokenFactory.java | 2 +- .../server/service/security/model/token/RawAccessJwtToken.java | 2 +- .../service/security/permission/AbstractPermissions.java | 2 +- .../service/security/permission/AccessControlService.java | 2 +- .../service/security/permission/CustomerUserPermissions.java | 2 +- .../security/permission/DefaultAccessControlService.java | 2 +- .../server/service/security/permission/Operation.java | 2 +- .../server/service/security/permission/PermissionChecker.java | 2 +- .../server/service/security/permission/Permissions.java | 2 +- .../server/service/security/permission/Resource.java | 2 +- .../service/security/permission/SysAdminPermissions.java | 2 +- .../service/security/permission/TenantAdminPermissions.java | 2 +- .../service/security/system/DefaultSystemSecurityService.java | 2 +- .../server/service/security/system/SystemSecurityService.java | 2 +- .../service/session/DefaultDeviceSessionCacheService.java | 2 +- .../server/service/session/DeviceSessionCacheService.java | 2 +- .../server/service/state/DefaultDeviceStateService.java | 2 +- .../java/org/thingsboard/server/service/state/DeviceState.java | 2 +- .../org/thingsboard/server/service/state/DeviceStateData.java | 2 +- .../thingsboard/server/service/state/DeviceStateService.java | 2 +- .../thingsboard/server/service/telemetry/AttributeData.java | 2 +- .../service/telemetry/DefaultTelemetrySubscriptionService.java | 2 +- .../service/telemetry/DefaultTelemetryWebSocketService.java | 2 +- .../org/thingsboard/server/service/telemetry/SessionEvent.java | 2 +- .../thingsboard/server/service/telemetry/TelemetryFeature.java | 2 +- .../server/service/telemetry/TelemetrySubscriptionService.java | 2 +- .../service/telemetry/TelemetryWebSocketMsgEndpoint.java | 2 +- .../server/service/telemetry/TelemetryWebSocketService.java | 2 +- .../server/service/telemetry/TelemetryWebSocketSessionRef.java | 2 +- .../server/service/telemetry/TelemetryWebSocketTextMsg.java | 2 +- .../java/org/thingsboard/server/service/telemetry/TsData.java | 2 +- .../server/service/telemetry/WsSessionMetaData.java | 2 +- .../service/telemetry/cmd/AttributesSubscriptionCmd.java | 2 +- .../server/service/telemetry/cmd/GetHistoryCmd.java | 2 +- .../server/service/telemetry/cmd/SubscriptionCmd.java | 2 +- .../server/service/telemetry/cmd/TelemetryPluginCmd.java | 2 +- .../service/telemetry/cmd/TelemetryPluginCmdsWrapper.java | 2 +- .../service/telemetry/cmd/TimeseriesSubscriptionCmd.java | 2 +- .../service/telemetry/exception/AccessDeniedException.java | 2 +- .../service/telemetry/exception/EntityNotFoundException.java | 2 +- .../service/telemetry/exception/InternalErrorException.java | 2 +- .../telemetry/exception/InvalidParametersException.java | 2 +- .../service/telemetry/exception/ToErrorResponseEntity.java | 2 +- .../service/telemetry/exception/UnauthorizedException.java | 2 +- .../service/telemetry/exception/UncheckedApiException.java | 2 +- .../thingsboard/server/service/telemetry/sub/Subscription.java | 2 +- .../server/service/telemetry/sub/SubscriptionErrorCode.java | 2 +- .../server/service/telemetry/sub/SubscriptionState.java | 2 +- .../server/service/telemetry/sub/SubscriptionUpdate.java | 2 +- .../service/transaction/BaseRuleChainTransactionService.java | 2 +- .../server/service/transaction/TbTransactionTask.java | 2 +- .../server/service/transport/LocalTransportApiService.java | 2 +- .../server/service/transport/LocalTransportService.java | 2 +- .../service/transport/RemoteRuleEngineTransportService.java | 2 +- .../server/service/transport/RemoteTransportApiService.java | 2 +- .../thingsboard/server/service/transport/RuleEngineStats.java | 2 +- .../server/service/transport/RuleEngineTransportService.java | 2 +- .../server/service/transport/ToRuleEngineMsgDecoder.java | 2 +- .../server/service/transport/ToTransportMsgEncoder.java | 2 +- .../server/service/transport/TransportApiRequestDecoder.java | 2 +- .../server/service/transport/TransportApiResponseEncoder.java | 2 +- .../server/service/transport/TransportApiService.java | 2 +- .../transport/msg/TransportToDeviceActorMsgWrapper.java | 2 +- .../server/service/update/DefaultUpdateService.java | 2 +- .../org/thingsboard/server/service/update/UpdateService.java | 2 +- .../src/main/java/org/thingsboard/server/utils/MiscUtils.java | 2 +- application/src/main/proto/cluster.proto | 2 +- application/src/main/proto/jsinvoke.proto | 2 +- application/src/main/resources/actor-system.conf | 2 +- application/src/main/resources/logback.xml | 2 +- application/src/main/resources/templates/account.activated.vm | 2 +- application/src/main/resources/templates/account.lockout.vm | 2 +- application/src/main/resources/templates/activation.vm | 2 +- application/src/main/resources/templates/password.was.reset.vm | 2 +- application/src/main/resources/templates/reset.password.vm | 2 +- application/src/main/resources/templates/test.vm | 2 +- application/src/main/resources/thingsboard.yml | 2 +- application/src/main/scripts/install/install.sh | 2 +- application/src/main/scripts/install/install_dev_db.sh | 2 +- application/src/main/scripts/install/logback.xml | 2 +- application/src/main/scripts/install/upgrade.sh | 2 +- application/src/main/scripts/install/upgrade_dev_db.sh | 2 +- .../thingsboard/server/controller/AbstractControllerTest.java | 2 +- .../server/controller/AbstractRuleEngineControllerTest.java | 2 +- .../thingsboard/server/controller/BaseAdminControllerTest.java | 2 +- .../thingsboard/server/controller/BaseAssetControllerTest.java | 2 +- .../server/controller/BaseAuditLogControllerTest.java | 2 +- .../thingsboard/server/controller/BaseAuthControllerTest.java | 2 +- .../controller/BaseComponentDescriptorControllerTest.java | 2 +- .../server/controller/BaseCustomerControllerTest.java | 2 +- .../server/controller/BaseDashboardControllerTest.java | 2 +- .../server/controller/BaseDeviceControllerTest.java | 2 +- .../server/controller/BaseEntityViewControllerTest.java | 2 +- .../server/controller/BaseTenantControllerTest.java | 2 +- .../thingsboard/server/controller/BaseUserControllerTest.java | 2 +- .../server/controller/BaseWidgetTypeControllerTest.java | 2 +- .../server/controller/BaseWidgetsBundleControllerTest.java | 2 +- .../server/controller/ControllerNoSqlTestSuite.java | 2 +- .../thingsboard/server/controller/ControllerSqlTestSuite.java | 2 +- .../server/controller/nosql/AdminControllerNoSqlTest.java | 2 +- .../server/controller/nosql/AssetControllerNoSqlTest.java | 2 +- .../server/controller/nosql/AuditLogControllerNoSqlTest.java | 2 +- .../server/controller/nosql/AuthControllerNoSqlTest.java | 2 +- .../nosql/ComponentDescriptorControllerNoSqlTest.java | 2 +- .../server/controller/nosql/CustomerControllerNoSqlTest.java | 2 +- .../server/controller/nosql/DashboardControllerNoSqlTest.java | 2 +- .../server/controller/nosql/DeviceControllerNoSqlTest.java | 2 +- .../server/controller/nosql/EntityViewControllerNoSqlTest.java | 2 +- .../server/controller/nosql/TenantControllerNoSqlTest.java | 2 +- .../server/controller/nosql/UserControllerNoSqlTest.java | 2 +- .../server/controller/nosql/WidgetTypeControllerNoSqlTest.java | 2 +- .../controller/nosql/WidgetsBundleControllerNoSqlTest.java | 2 +- .../server/controller/sql/AdminControllerSqlTest.java | 2 +- .../server/controller/sql/AssetControllerSqlTest.java | 2 +- .../server/controller/sql/AuditLogControllerSqlTest.java | 2 +- .../server/controller/sql/AuthControllerSqlTest.java | 2 +- .../controller/sql/ComponentDescriptorControllerSqlTest.java | 2 +- .../server/controller/sql/CustomerControllerSqlTest.java | 2 +- .../server/controller/sql/DashboardControllerSqlTest.java | 2 +- .../server/controller/sql/DeviceControllerSqlTest.java | 2 +- .../server/controller/sql/EntityViewControllerSqlTest.java | 2 +- .../server/controller/sql/TenantControllerSqlTest.java | 2 +- .../server/controller/sql/UserControllerSqlTest.java | 2 +- .../server/controller/sql/WidgetTypeControllerSqlTest.java | 2 +- .../server/controller/sql/WidgetsBundleControllerSqlTest.java | 2 +- .../org/thingsboard/server/mqtt/DbConfigurationTestRule.java | 2 +- .../java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java | 2 +- .../java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java | 2 +- .../mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java | 2 +- .../mqtt/rpc/nosql/MqttServerSideRpcNoSqlIntegrationTest.java | 2 +- .../mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java | 2 +- .../mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java | 2 +- .../telemetry/nosql/MqttTelemetryNoSqlIntegrationTest.java | 2 +- .../mqtt/telemetry/sql/MqttTelemetrySqlIntegrationTest.java | 2 +- .../org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java | 2 +- .../org/thingsboard/server/rules/RuleEngineSqlTestSuite.java | 2 +- .../rules/flow/AbstractRuleEngineFlowIntegrationTest.java | 2 +- .../rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java | 2 +- .../rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java | 2 +- .../lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java | 2 +- .../nosql/RuleEngineLifecycleNoSqlIntegrationTest.java | 2 +- .../lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java | 2 +- .../cluster/routing/ConsistentClusterRoutingServiceTest.java | 2 +- .../org/thingsboard/server/service/mail/TestMailService.java | 2 +- .../server/service/script/RuleNodeJsScriptEngineTest.java | 2 +- .../server/service/script/TestNashornJsInvokeService.java | 2 +- .../org/thingsboard/server/system/BaseHttpDeviceApiTest.java | 2 +- .../org/thingsboard/server/system/SystemNoSqlTestSuite.java | 2 +- .../java/org/thingsboard/server/system/SystemSqlTestSuite.java | 2 +- .../thingsboard/server/system/nosql/DeviceApiNoSqlTest.java | 2 +- .../org/thingsboard/server/system/sql/DeviceApiSqlTest.java | 2 +- common/dao-api/pom.xml | 2 +- .../java/org/thingsboard/server/dao/alarm/AlarmService.java | 2 +- .../java/org/thingsboard/server/dao/asset/AssetService.java | 2 +- .../thingsboard/server/dao/attributes/AttributesService.java | 2 +- .../java/org/thingsboard/server/dao/audit/AuditLogService.java | 2 +- .../server/dao/cassandra/AbstractCassandraCluster.java | 2 +- .../org/thingsboard/server/dao/cassandra/CassandraCluster.java | 2 +- .../server/dao/cassandra/CassandraInstallCluster.java | 2 +- .../server/dao/cassandra/CassandraQueryOptions.java | 2 +- .../server/dao/cassandra/CassandraSocketOptions.java | 2 +- .../server/dao/component/ComponentDescriptorService.java | 2 +- .../org/thingsboard/server/dao/customer/CustomerService.java | 2 +- .../org/thingsboard/server/dao/dashboard/DashboardService.java | 2 +- .../org/thingsboard/server/dao/device/ClaimDevicesService.java | 2 +- .../server/dao/device/DeviceCredentialsService.java | 2 +- .../java/org/thingsboard/server/dao/device/DeviceService.java | 2 +- .../org/thingsboard/server/dao/device/claim/ClaimData.java | 2 +- .../org/thingsboard/server/dao/device/claim/ClaimResponse.java | 2 +- .../org/thingsboard/server/dao/device/claim/ClaimResult.java | 2 +- .../java/org/thingsboard/server/dao/entity/EntityService.java | 2 +- .../thingsboard/server/dao/entityview/EntityViewService.java | 2 +- .../java/org/thingsboard/server/dao/event/EventService.java | 2 +- .../thingsboard/server/dao/nosql/CassandraStatementTask.java | 2 +- .../org/thingsboard/server/dao/relation/RelationService.java | 2 +- .../java/org/thingsboard/server/dao/rule/RuleChainService.java | 2 +- .../thingsboard/server/dao/settings/AdminSettingsService.java | 2 +- .../java/org/thingsboard/server/dao/tenant/TenantService.java | 2 +- .../thingsboard/server/dao/timeseries/TimeseriesService.java | 2 +- .../main/java/org/thingsboard/server/dao/user/UserService.java | 2 +- .../main/java/org/thingsboard/server/dao/util/AsyncTask.java | 2 +- .../src/main/java/org/thingsboard/server/dao/util/HsqlDao.java | 2 +- .../main/java/org/thingsboard/server/dao/util/NoSqlAnyDao.java | 2 +- .../main/java/org/thingsboard/server/dao/util/NoSqlDao.java | 2 +- .../main/java/org/thingsboard/server/dao/util/NoSqlTsDao.java | 2 +- .../src/main/java/org/thingsboard/server/dao/util/PsqlDao.java | 2 +- .../src/main/java/org/thingsboard/server/dao/util/SqlDao.java | 2 +- .../main/java/org/thingsboard/server/dao/util/SqlTsDao.java | 2 +- .../org/thingsboard/server/dao/widget/WidgetTypeService.java | 2 +- .../thingsboard/server/dao/widget/WidgetsBundleService.java | 2 +- common/data/pom.xml | 2 +- .../java/org/thingsboard/server/common/data/AdminSettings.java | 2 +- .../main/java/org/thingsboard/server/common/data/BaseData.java | 2 +- .../org/thingsboard/server/common/data/CacheConstants.java | 2 +- .../java/org/thingsboard/server/common/data/ClaimRequest.java | 2 +- .../java/org/thingsboard/server/common/data/ContactBased.java | 2 +- .../main/java/org/thingsboard/server/common/data/Customer.java | 2 +- .../java/org/thingsboard/server/common/data/Dashboard.java | 2 +- .../java/org/thingsboard/server/common/data/DashboardInfo.java | 2 +- .../java/org/thingsboard/server/common/data/DataConstants.java | 2 +- .../main/java/org/thingsboard/server/common/data/Device.java | 2 +- .../org/thingsboard/server/common/data/EntityFieldsData.java | 2 +- .../java/org/thingsboard/server/common/data/EntitySubtype.java | 2 +- .../java/org/thingsboard/server/common/data/EntityType.java | 2 +- .../java/org/thingsboard/server/common/data/EntityView.java | 2 +- .../main/java/org/thingsboard/server/common/data/Event.java | 2 +- .../org/thingsboard/server/common/data/HasAdditionalInfo.java | 2 +- .../java/org/thingsboard/server/common/data/HasCustomerId.java | 2 +- .../main/java/org/thingsboard/server/common/data/HasName.java | 2 +- .../java/org/thingsboard/server/common/data/HasTenantId.java | 2 +- .../org/thingsboard/server/common/data/SearchTextBased.java | 2 +- .../server/common/data/SearchTextBasedWithAdditionalInfo.java | 2 +- .../org/thingsboard/server/common/data/ShortCustomerInfo.java | 2 +- .../main/java/org/thingsboard/server/common/data/Tenant.java | 2 +- .../java/org/thingsboard/server/common/data/UUIDConverter.java | 2 +- .../java/org/thingsboard/server/common/data/UpdateMessage.java | 2 +- .../src/main/java/org/thingsboard/server/common/data/User.java | 2 +- .../java/org/thingsboard/server/common/data/alarm/Alarm.java | 2 +- .../java/org/thingsboard/server/common/data/alarm/AlarmId.java | 2 +- .../org/thingsboard/server/common/data/alarm/AlarmInfo.java | 2 +- .../org/thingsboard/server/common/data/alarm/AlarmQuery.java | 2 +- .../server/common/data/alarm/AlarmSearchStatus.java | 2 +- .../thingsboard/server/common/data/alarm/AlarmSeverity.java | 2 +- .../org/thingsboard/server/common/data/alarm/AlarmStatus.java | 2 +- .../java/org/thingsboard/server/common/data/asset/Asset.java | 2 +- .../thingsboard/server/common/data/asset/AssetSearchQuery.java | 2 +- .../org/thingsboard/server/common/data/audit/ActionStatus.java | 2 +- .../org/thingsboard/server/common/data/audit/ActionType.java | 2 +- .../org/thingsboard/server/common/data/audit/AuditLog.java | 2 +- .../server/common/data/device/DeviceSearchQuery.java | 2 +- .../server/common/data/entityview/EntityViewSearchQuery.java | 2 +- .../server/common/data/exception/ThingsboardErrorCode.java | 2 +- .../server/common/data/exception/ThingsboardException.java | 2 +- .../org/thingsboard/server/common/data/id/AdminSettingsId.java | 2 +- .../java/org/thingsboard/server/common/data/id/AssetId.java | 2 +- .../java/org/thingsboard/server/common/data/id/AuditLogId.java | 2 +- .../server/common/data/id/ComponentDescriptorId.java | 2 +- .../java/org/thingsboard/server/common/data/id/CustomerId.java | 2 +- .../org/thingsboard/server/common/data/id/DashboardId.java | 2 +- .../thingsboard/server/common/data/id/DeviceCredentialsId.java | 2 +- .../java/org/thingsboard/server/common/data/id/DeviceId.java | 2 +- .../java/org/thingsboard/server/common/data/id/EntityId.java | 2 +- .../server/common/data/id/EntityIdDeserializer.java | 2 +- .../org/thingsboard/server/common/data/id/EntityIdFactory.java | 2 +- .../thingsboard/server/common/data/id/EntityIdSerializer.java | 2 +- .../org/thingsboard/server/common/data/id/EntityViewId.java | 2 +- .../java/org/thingsboard/server/common/data/id/EventId.java | 2 +- .../java/org/thingsboard/server/common/data/id/IdBased.java | 2 +- .../java/org/thingsboard/server/common/data/id/NodeId.java | 2 +- .../org/thingsboard/server/common/data/id/RuleChainId.java | 2 +- .../java/org/thingsboard/server/common/data/id/RuleNodeId.java | 2 +- .../java/org/thingsboard/server/common/data/id/TenantId.java | 2 +- .../java/org/thingsboard/server/common/data/id/UUIDBased.java | 2 +- .../thingsboard/server/common/data/id/UserCredentialsId.java | 2 +- .../java/org/thingsboard/server/common/data/id/UserId.java | 2 +- .../org/thingsboard/server/common/data/id/WidgetTypeId.java | 2 +- .../org/thingsboard/server/common/data/id/WidgetsBundleId.java | 2 +- .../org/thingsboard/server/common/data/kv/Aggregation.java | 2 +- .../org/thingsboard/server/common/data/kv/AttributeKey.java | 2 +- .../thingsboard/server/common/data/kv/AttributeKvEntry.java | 2 +- .../server/common/data/kv/BaseAttributeKvEntry.java | 2 +- .../thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java | 2 +- .../thingsboard/server/common/data/kv/BaseReadTsKvQuery.java | 2 +- .../org/thingsboard/server/common/data/kv/BaseTsKvQuery.java | 2 +- .../org/thingsboard/server/common/data/kv/BasicKvEntry.java | 2 +- .../org/thingsboard/server/common/data/kv/BasicTsKvEntry.java | 2 +- .../thingsboard/server/common/data/kv/BooleanDataEntry.java | 2 +- .../java/org/thingsboard/server/common/data/kv/DataType.java | 2 +- .../org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java | 2 +- .../org/thingsboard/server/common/data/kv/DoubleDataEntry.java | 2 +- .../java/org/thingsboard/server/common/data/kv/KvEntry.java | 2 +- .../org/thingsboard/server/common/data/kv/LongDataEntry.java | 2 +- .../org/thingsboard/server/common/data/kv/ReadTsKvQuery.java | 2 +- .../org/thingsboard/server/common/data/kv/StringDataEntry.java | 2 +- .../java/org/thingsboard/server/common/data/kv/TsKvEntry.java | 2 +- .../java/org/thingsboard/server/common/data/kv/TsKvQuery.java | 2 +- .../server/common/data/objects/AttributesEntityView.java | 2 +- .../server/common/data/objects/TelemetryEntityView.java | 2 +- .../org/thingsboard/server/common/data/page/BasePageLink.java | 2 +- .../thingsboard/server/common/data/page/PageDataIterable.java | 2 +- .../org/thingsboard/server/common/data/page/TextPageData.java | 2 +- .../org/thingsboard/server/common/data/page/TextPageLink.java | 2 +- .../org/thingsboard/server/common/data/page/TimePageData.java | 2 +- .../org/thingsboard/server/common/data/page/TimePageLink.java | 2 +- .../server/common/data/plugin/ComponentDescriptor.java | 2 +- .../server/common/data/plugin/ComponentLifecycleEvent.java | 2 +- .../server/common/data/plugin/ComponentLifecycleState.java | 2 +- .../thingsboard/server/common/data/plugin/ComponentScope.java | 2 +- .../thingsboard/server/common/data/plugin/ComponentType.java | 2 +- .../server/common/data/relation/EntityRelation.java | 2 +- .../server/common/data/relation/EntityRelationInfo.java | 2 +- .../server/common/data/relation/EntityRelationsQuery.java | 2 +- .../server/common/data/relation/EntitySearchDirection.java | 2 +- .../server/common/data/relation/EntityTypeFilter.java | 2 +- .../server/common/data/relation/RelationTypeGroup.java | 2 +- .../server/common/data/relation/RelationsSearchParameters.java | 2 +- .../org/thingsboard/server/common/data/rpc/RpcRequest.java | 2 +- .../server/common/data/rpc/ToDeviceRpcRequestBody.java | 2 +- .../server/common/data/rule/NodeConnectionInfo.java | 2 +- .../org/thingsboard/server/common/data/rule/RuleChain.java | 2 +- .../server/common/data/rule/RuleChainConnectionInfo.java | 2 +- .../thingsboard/server/common/data/rule/RuleChainMetaData.java | 2 +- .../java/org/thingsboard/server/common/data/rule/RuleNode.java | 2 +- .../java/org/thingsboard/server/common/data/rule/RuleType.java | 2 +- .../java/org/thingsboard/server/common/data/rule/Scope.java | 2 +- .../org/thingsboard/server/common/data/security/Authority.java | 2 +- .../server/common/data/security/DeviceCredentials.java | 2 +- .../server/common/data/security/DeviceCredentialsFilter.java | 2 +- .../server/common/data/security/DeviceCredentialsType.java | 2 +- .../server/common/data/security/DeviceTokenCredentials.java | 2 +- .../server/common/data/security/DeviceX509Credentials.java | 2 +- .../server/common/data/security/UserCredentials.java | 2 +- .../server/common/data/security/model/SecuritySettings.java | 2 +- .../server/common/data/security/model/UserPasswordPolicy.java | 2 +- .../org/thingsboard/server/common/data/widget/WidgetType.java | 2 +- .../thingsboard/server/common/data/widget/WidgetsBundle.java | 2 +- .../org/thingsboard/server/common/data/UUIDConverterTest.java | 2 +- common/message/pom.xml | 2 +- .../java/org/thingsboard/server/common/msg/EncryptionUtil.java | 2 +- .../main/java/org/thingsboard/server/common/msg/MsgType.java | 2 +- .../java/org/thingsboard/server/common/msg/TbActorMsg.java | 2 +- .../src/main/java/org/thingsboard/server/common/msg/TbMsg.java | 2 +- .../java/org/thingsboard/server/common/msg/TbMsgDataType.java | 2 +- .../java/org/thingsboard/server/common/msg/TbMsgMetaData.java | 2 +- .../thingsboard/server/common/msg/TbMsgTransactionData.java | 2 +- .../thingsboard/server/common/msg/aware/CustomerAwareMsg.java | 2 +- .../thingsboard/server/common/msg/aware/DeviceAwareMsg.java | 2 +- .../org/thingsboard/server/common/msg/aware/NodeAwareMsg.java | 2 +- .../thingsboard/server/common/msg/aware/RuleChainAwareMsg.java | 2 +- .../thingsboard/server/common/msg/aware/TenantAwareMsg.java | 2 +- .../thingsboard/server/common/msg/cluster/ClusterEventMsg.java | 2 +- .../server/common/msg/cluster/SendToClusterMsg.java | 2 +- .../thingsboard/server/common/msg/cluster/ServerAddress.java | 2 +- .../org/thingsboard/server/common/msg/cluster/ServerType.java | 2 +- .../thingsboard/server/common/msg/cluster/ToAllNodesMsg.java | 2 +- .../server/common/msg/core/ToServerRpcResponseMsg.java | 2 +- .../org/thingsboard/server/common/msg/kv/AttributesKVMsg.java | 2 +- .../thingsboard/server/common/msg/kv/BasicAttributeKVMsg.java | 2 +- .../server/common/msg/plugin/ComponentLifecycleMsg.java | 2 +- .../thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java | 2 +- .../org/thingsboard/server/common/msg/session/FeatureType.java | 2 +- .../thingsboard/server/common/msg/session/SessionContext.java | 2 +- .../thingsboard/server/common/msg/session/SessionMsgType.java | 2 +- .../common/msg/session/ex/ProcessingTimeoutException.java | 2 +- .../server/common/msg/session/ex/SessionAuthException.java | 2 +- .../server/common/msg/session/ex/SessionException.java | 2 +- .../server/common/msg/system/ServiceToRuleEngineMsg.java | 2 +- .../common/msg/timeout/DeviceActorClientSideRpcTimeoutMsg.java | 2 +- .../common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java | 2 +- .../org/thingsboard/server/common/msg/timeout/TimeoutMsg.java | 2 +- .../org/thingsboard/server/common/msg/tools/TbRateLimits.java | 2 +- .../server/common/msg/tools/TbRateLimitsException.java | 2 +- common/message/src/main/proto/tbmsg.proto | 2 +- common/pom.xml | 2 +- common/queue/pom.xml | 2 +- .../org/thingsboard/server/kafka/AbstractTbKafkaTemplate.java | 2 +- .../org/thingsboard/server/kafka/AsyncCallbackTemplate.java | 2 +- .../main/java/org/thingsboard/server/kafka/TBKafkaAdmin.java | 2 +- .../org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java | 2 +- .../org/thingsboard/server/kafka/TBKafkaProducerTemplate.java | 2 +- .../main/java/org/thingsboard/server/kafka/TbKafkaDecoder.java | 2 +- .../main/java/org/thingsboard/server/kafka/TbKafkaEncoder.java | 2 +- .../main/java/org/thingsboard/server/kafka/TbKafkaHandler.java | 2 +- .../java/org/thingsboard/server/kafka/TbKafkaPartitioner.java | 2 +- .../java/org/thingsboard/server/kafka/TbKafkaProperty.java | 2 +- .../thingsboard/server/kafka/TbKafkaRequestIdExtractor.java | 2 +- .../org/thingsboard/server/kafka/TbKafkaRequestTemplate.java | 2 +- .../org/thingsboard/server/kafka/TbKafkaResponseTemplate.java | 2 +- .../java/org/thingsboard/server/kafka/TbKafkaSettings.java | 2 +- .../java/org/thingsboard/server/kafka/TbNodeIdProvider.java | 2 +- common/transport/coap/pom.xml | 2 +- .../server/transport/coap/CoapTransportContext.java | 2 +- .../server/transport/coap/CoapTransportResource.java | 2 +- .../server/transport/coap/CoapTransportService.java | 2 +- .../server/transport/coap/adaptors/CoapTransportAdaptor.java | 2 +- .../server/transport/coap/adaptors/JsonCoapAdaptor.java | 2 +- .../server/transport/coap/client/DeviceEmulator.java | 2 +- common/transport/http/pom.xml | 2 +- .../thingsboard/server/transport/http/DeviceApiController.java | 2 +- .../server/transport/http/HttpTransportContext.java | 2 +- common/transport/mqtt/pom.xml | 2 +- .../server/transport/mqtt/MqttSslHandlerProvider.java | 2 +- .../java/org/thingsboard/server/transport/mqtt/MqttTopics.java | 2 +- .../server/transport/mqtt/MqttTransportContext.java | 2 +- .../server/transport/mqtt/MqttTransportHandler.java | 2 +- .../server/transport/mqtt/MqttTransportServerInitializer.java | 2 +- .../server/transport/mqtt/MqttTransportService.java | 2 +- .../server/transport/mqtt/adaptors/JsonMqttAdaptor.java | 2 +- .../server/transport/mqtt/adaptors/MqttTransportAdaptor.java | 2 +- .../server/transport/mqtt/session/DeviceSessionCtx.java | 2 +- .../server/transport/mqtt/session/GatewayDeviceSessionCtx.java | 2 +- .../server/transport/mqtt/session/GatewaySessionHandler.java | 2 +- .../transport/mqtt/session/MqttDeviceAwareSessionContext.java | 2 +- .../server/transport/mqtt/session/MqttTopicMatcher.java | 2 +- .../org/thingsboard/server/transport/mqtt/util/SslUtil.java | 2 +- common/transport/pom.xml | 2 +- common/transport/transport-api/pom.xml | 2 +- .../server/common/transport/SessionMsgListener.java | 2 +- .../server/common/transport/SessionMsgProcessor.java | 2 +- .../thingsboard/server/common/transport/TransportAdaptor.java | 2 +- .../thingsboard/server/common/transport/TransportContext.java | 2 +- .../thingsboard/server/common/transport/TransportService.java | 2 +- .../server/common/transport/TransportServiceCallback.java | 2 +- .../server/common/transport/adaptor/AdaptorException.java | 2 +- .../server/common/transport/adaptor/JsonConverter.java | 2 +- .../server/common/transport/adaptor/JsonConverterConfig.java | 2 +- .../server/common/transport/auth/DeviceAuthResult.java | 2 +- .../server/common/transport/auth/DeviceAuthService.java | 2 +- .../common/transport/service/AbstractTransportService.java | 2 +- .../common/transport/service/RemoteTransportService.java | 2 +- .../server/common/transport/service/SessionMetaData.java | 2 +- .../common/transport/service/ToRuleEngineMsgEncoder.java | 2 +- .../transport/service/ToTransportMsgResponseDecoder.java | 2 +- .../common/transport/service/TransportApiRequestEncoder.java | 2 +- .../common/transport/service/TransportApiResponseDecoder.java | 2 +- .../common/transport/session/DeviceAwareSessionContext.java | 2 +- common/transport/transport-api/src/main/proto/transport.proto | 2 +- common/util/pom.xml | 2 +- .../org/thingsboard/common/util/AbstractListeningExecutor.java | 2 +- .../main/java/org/thingsboard/common/util/DonAsynchron.java | 2 +- .../java/org/thingsboard/common/util/ListeningExecutor.java | 2 +- .../org/thingsboard/common/util/ThingsBoardThreadFactory.java | 2 +- dao/pom.xml | 2 +- dao/src/main/java/org/thingsboard/server/dao/Dao.java | 2 +- dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java | 2 +- dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java | 2 +- .../main/java/org/thingsboard/server/dao/NoSqlDaoConfig.java | 2 +- .../main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java | 2 +- .../java/org/thingsboard/server/dao/TimescaleDaoConfig.java | 2 +- .../main/java/org/thingsboard/server/dao/alarm/AlarmDao.java | 2 +- .../org/thingsboard/server/dao/alarm/BaseAlarmService.java | 2 +- .../org/thingsboard/server/dao/alarm/CassandraAlarmDao.java | 2 +- .../main/java/org/thingsboard/server/dao/asset/AssetDao.java | 2 +- .../java/org/thingsboard/server/dao/asset/AssetTypeFilter.java | 2 +- .../org/thingsboard/server/dao/asset/BaseAssetService.java | 2 +- .../org/thingsboard/server/dao/asset/CassandraAssetDao.java | 2 +- .../org/thingsboard/server/dao/attributes/AttributesDao.java | 2 +- .../server/dao/attributes/BaseAttributesService.java | 2 +- .../server/dao/attributes/CassandraBaseAttributesDao.java | 2 +- .../java/org/thingsboard/server/dao/audit/AuditLogDao.java | 2 +- .../org/thingsboard/server/dao/audit/AuditLogLevelFilter.java | 2 +- .../org/thingsboard/server/dao/audit/AuditLogLevelMask.java | 2 +- .../org/thingsboard/server/dao/audit/AuditLogQueryCursor.java | 2 +- .../org/thingsboard/server/dao/audit/AuditLogServiceImpl.java | 2 +- .../org/thingsboard/server/dao/audit/CassandraAuditLogDao.java | 2 +- .../thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java | 2 +- .../org/thingsboard/server/dao/audit/sink/AuditLogSink.java | 2 +- .../thingsboard/server/dao/audit/sink/DummyAuditLogSink.java | 2 +- .../server/dao/audit/sink/ElasticsearchAuditLogSink.java | 2 +- .../main/java/org/thingsboard/server/dao/cache/CacheSpecs.java | 2 +- .../server/dao/cache/CaffeineCacheConfiguration.java | 2 +- .../dao/cache/PreviousDeviceCredentialsIdKeyGenerator.java | 2 +- .../server/dao/cache/TBRedisCacheConfiguration.java | 2 +- .../server/dao/cache/TBRedisClusterConfiguration.java | 2 +- .../server/dao/cache/TBRedisStandaloneConfiguration.java | 2 +- .../server/dao/component/BaseComponentDescriptorService.java | 2 +- .../dao/component/CassandraBaseComponentDescriptorDao.java | 2 +- .../server/dao/component/ComponentDescriptorDao.java | 2 +- .../thingsboard/server/dao/customer/CassandraCustomerDao.java | 2 +- .../java/org/thingsboard/server/dao/customer/CustomerDao.java | 2 +- .../thingsboard/server/dao/customer/CustomerServiceImpl.java | 2 +- .../server/dao/dashboard/CassandraDashboardDao.java | 2 +- .../server/dao/dashboard/CassandraDashboardInfoDao.java | 2 +- .../org/thingsboard/server/dao/dashboard/DashboardDao.java | 2 +- .../org/thingsboard/server/dao/dashboard/DashboardInfoDao.java | 2 +- .../thingsboard/server/dao/dashboard/DashboardServiceImpl.java | 2 +- .../server/dao/device/CassandraDeviceCredentialsDao.java | 2 +- .../org/thingsboard/server/dao/device/CassandraDeviceDao.java | 2 +- .../java/org/thingsboard/server/dao/device/ClaimDataInfo.java | 2 +- .../thingsboard/server/dao/device/ClaimDevicesServiceImpl.java | 2 +- .../thingsboard/server/dao/device/DeviceCredentialsDao.java | 2 +- .../server/dao/device/DeviceCredentialsServiceImpl.java | 2 +- .../main/java/org/thingsboard/server/dao/device/DeviceDao.java | 2 +- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 2 +- .../thingsboard/server/dao/entity/AbstractEntityService.java | 2 +- .../org/thingsboard/server/dao/entity/BaseEntityService.java | 2 +- .../server/dao/entityview/CassandraEntityViewDao.java | 2 +- .../org/thingsboard/server/dao/entityview/EntityViewDao.java | 2 +- .../server/dao/entityview/EntityViewServiceImpl.java | 2 +- .../org/thingsboard/server/dao/event/BaseEventService.java | 2 +- .../thingsboard/server/dao/event/CassandraBaseEventDao.java | 2 +- .../main/java/org/thingsboard/server/dao/event/EventDao.java | 2 +- .../thingsboard/server/dao/exception/BufferLimitException.java | 2 +- .../server/dao/exception/DataValidationException.java | 2 +- .../thingsboard/server/dao/exception/DatabaseException.java | 2 +- .../server/dao/exception/IncorrectParameterException.java | 2 +- .../main/java/org/thingsboard/server/dao/model/BaseEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/BaseSqlEntity.java | 2 +- .../org/thingsboard/server/dao/model/EntitySubtypeEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/ModelConstants.java | 2 +- .../org/thingsboard/server/dao/model/SearchTextEntity.java | 2 +- dao/src/main/java/org/thingsboard/server/dao/model/ToData.java | 2 +- .../server/dao/model/nosql/AdminSettingsEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/AlarmEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/AssetEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/AuditLogEntity.java | 2 +- .../server/dao/model/nosql/ComponentDescriptorEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/CustomerEntity.java | 2 +- .../thingsboard/server/dao/model/nosql/DashboardEntity.java | 2 +- .../server/dao/model/nosql/DashboardInfoEntity.java | 2 +- .../server/dao/model/nosql/DeviceCredentialsEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/DeviceEntity.java | 2 +- .../thingsboard/server/dao/model/nosql/EntityViewEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/EventEntity.java | 2 +- .../thingsboard/server/dao/model/nosql/RuleChainEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/TenantEntity.java | 2 +- .../server/dao/model/nosql/UserCredentialsEntity.java | 2 +- .../org/thingsboard/server/dao/model/nosql/UserEntity.java | 2 +- .../thingsboard/server/dao/model/nosql/WidgetTypeEntity.java | 2 +- .../server/dao/model/nosql/WidgetsBundleEntity.java | 2 +- .../thingsboard/server/dao/model/sql/AbstractTsKvEntity.java | 2 +- .../thingsboard/server/dao/model/sql/AdminSettingsEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/sql/AlarmEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/sql/AssetEntity.java | 2 +- .../server/dao/model/sql/AttributeKvCompositeKey.java | 2 +- .../thingsboard/server/dao/model/sql/AttributeKvEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/AuditLogEntity.java | 2 +- .../server/dao/model/sql/ComponentDescriptorEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/CustomerEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/DashboardEntity.java | 2 +- .../thingsboard/server/dao/model/sql/DashboardInfoEntity.java | 2 +- .../server/dao/model/sql/DeviceCredentialsEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/DeviceEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/EntityViewEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/sql/EventEntity.java | 2 +- .../thingsboard/server/dao/model/sql/RelationCompositeKey.java | 2 +- .../org/thingsboard/server/dao/model/sql/RelationEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/RuleChainEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/RuleNodeEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/TenantEntity.java | 2 +- .../server/dao/model/sql/UserCredentialsEntity.java | 2 +- .../java/org/thingsboard/server/dao/model/sql/UserEntity.java | 2 +- .../org/thingsboard/server/dao/model/sql/WidgetTypeEntity.java | 2 +- .../thingsboard/server/dao/model/sql/WidgetsBundleEntity.java | 2 +- .../dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java | 2 +- .../server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java | 2 +- .../server/dao/model/sqlts/ts/TsKvCompositeKey.java | 2 +- .../org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java | 2 +- .../server/dao/model/sqlts/ts/TsKvLatestCompositeKey.java | 2 +- .../server/dao/model/sqlts/ts/TsKvLatestEntity.java | 2 +- .../thingsboard/server/dao/model/type/ActionStatusCodec.java | 2 +- .../org/thingsboard/server/dao/model/type/ActionTypeCodec.java | 2 +- .../thingsboard/server/dao/model/type/AlarmSeverityCodec.java | 2 +- .../thingsboard/server/dao/model/type/AlarmStatusCodec.java | 2 +- .../org/thingsboard/server/dao/model/type/AuthorityCodec.java | 2 +- .../server/dao/model/type/ComponentLifecycleStateCodec.java | 2 +- .../thingsboard/server/dao/model/type/ComponentScopeCodec.java | 2 +- .../thingsboard/server/dao/model/type/ComponentTypeCodec.java | 2 +- .../server/dao/model/type/DeviceCredentialsTypeCodec.java | 2 +- .../org/thingsboard/server/dao/model/type/EntityTypeCodec.java | 2 +- .../java/org/thingsboard/server/dao/model/type/JsonCodec.java | 2 +- .../server/dao/model/type/RelationTypeGroupCodec.java | 2 +- .../thingsboard/server/dao/model/wrapper/EntityResultSet.java | 2 +- .../server/dao/nosql/CassandraAbstractAsyncDao.java | 2 +- .../org/thingsboard/server/dao/nosql/CassandraAbstractDao.java | 2 +- .../server/dao/nosql/CassandraAbstractModelDao.java | 2 +- .../server/dao/nosql/CassandraAbstractSearchTextDao.java | 2 +- .../server/dao/nosql/CassandraAbstractSearchTimeDao.java | 2 +- .../server/dao/nosql/CassandraBufferedRateExecutor.java | 2 +- .../server/dao/nosql/RateLimitedResultSetFuture.java | 2 +- .../org/thingsboard/server/dao/nosql/TbResultSetFuture.java | 2 +- .../org/thingsboard/server/dao/relation/BaseRelationDao.java | 2 +- .../thingsboard/server/dao/relation/BaseRelationService.java | 2 +- .../java/org/thingsboard/server/dao/relation/RelationDao.java | 2 +- .../org/thingsboard/server/dao/rule/BaseRuleChainService.java | 2 +- .../org/thingsboard/server/dao/rule/CassandraRuleChainDao.java | 2 +- .../org/thingsboard/server/dao/rule/CassandraRuleNodeDao.java | 2 +- .../java/org/thingsboard/server/dao/rule/RuleChainDao.java | 2 +- .../main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java | 2 +- .../java/org/thingsboard/server/dao/service/DataValidator.java | 2 +- .../org/thingsboard/server/dao/service/PaginatedRemover.java | 2 +- .../thingsboard/server/dao/service/TimePaginatedRemover.java | 2 +- .../java/org/thingsboard/server/dao/service/Validator.java | 2 +- .../org/thingsboard/server/dao/settings/AdminSettingsDao.java | 2 +- .../server/dao/settings/AdminSettingsServiceImpl.java | 2 +- .../server/dao/settings/CassandraAdminSettingsDao.java | 2 +- .../java/org/thingsboard/server/dao/sql/JpaAbstractDao.java | 2 +- .../server/dao/sql/JpaAbstractDaoListeningExecutorService.java | 2 +- .../thingsboard/server/dao/sql/JpaAbstractSearchTextDao.java | 2 +- .../thingsboard/server/dao/sql/JpaAbstractSearchTimeDao.java | 2 +- .../org/thingsboard/server/dao/sql/JpaExecutorService.java | 2 +- .../server/dao/sql/ScheduledLogExecutorComponent.java | 2 +- .../org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java | 2 +- .../thingsboard/server/dao/sql/TbSqlBlockingQueueParams.java | 2 +- .../main/java/org/thingsboard/server/dao/sql/TbSqlQueue.java | 2 +- .../java/org/thingsboard/server/dao/sql/TbSqlQueueElement.java | 2 +- .../org/thingsboard/server/dao/sql/alarm/AlarmRepository.java | 2 +- .../java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java | 2 +- .../org/thingsboard/server/dao/sql/asset/AssetRepository.java | 2 +- .../java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java | 2 +- .../server/dao/sql/attributes/AttributeKvInsertRepository.java | 2 +- .../server/dao/sql/attributes/AttributeKvRepository.java | 2 +- .../dao/sql/attributes/HsqlAttributesInsertRepository.java | 2 +- .../thingsboard/server/dao/sql/attributes/JpaAttributeDao.java | 2 +- .../dao/sql/attributes/PsqlAttributesInsertRepository.java | 2 +- .../thingsboard/server/dao/sql/audit/AuditLogRepository.java | 2 +- .../org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java | 2 +- .../component/AbstractComponentDescriptorInsertRepository.java | 2 +- .../dao/sql/component/ComponentDescriptorInsertRepository.java | 2 +- .../dao/sql/component/ComponentDescriptorRepository.java | 2 +- .../sql/component/HsqlComponentDescriptorInsertRepository.java | 2 +- .../dao/sql/component/JpaBaseComponentDescriptorDao.java | 2 +- .../sql/component/PsqlComponentDescriptorInsertRepository.java | 2 +- .../server/dao/sql/customer/CustomerRepository.java | 2 +- .../thingsboard/server/dao/sql/customer/JpaCustomerDao.java | 2 +- .../server/dao/sql/dashboard/DashboardInfoRepository.java | 2 +- .../server/dao/sql/dashboard/DashboardRepository.java | 2 +- .../thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java | 2 +- .../server/dao/sql/dashboard/JpaDashboardInfoDao.java | 2 +- .../server/dao/sql/device/DeviceCredentialsRepository.java | 2 +- .../thingsboard/server/dao/sql/device/DeviceRepository.java | 2 +- .../server/dao/sql/device/JpaDeviceCredentialsDao.java | 2 +- .../org/thingsboard/server/dao/sql/device/JpaDeviceDao.java | 2 +- .../server/dao/sql/entityview/EntityViewRepository.java | 2 +- .../server/dao/sql/entityview/JpaEntityViewDao.java | 2 +- .../server/dao/sql/event/AbstractEventInsertRepository.java | 2 +- .../server/dao/sql/event/EventInsertRepository.java | 2 +- .../org/thingsboard/server/dao/sql/event/EventRepository.java | 2 +- .../server/dao/sql/event/HsqlEventInsertRepository.java | 2 +- .../org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java | 2 +- .../server/dao/sql/event/PsqlEventInsertRepository.java | 2 +- .../thingsboard/server/dao/sql/relation/JpaRelationDao.java | 2 +- .../server/dao/sql/relation/RelationRepository.java | 2 +- .../org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java | 2 +- .../org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java | 2 +- .../thingsboard/server/dao/sql/rule/RuleChainRepository.java | 2 +- .../thingsboard/server/dao/sql/rule/RuleNodeRepository.java | 2 +- .../server/dao/sql/settings/AdminSettingsRepository.java | 2 +- .../server/dao/sql/settings/JpaAdminSettingsDao.java | 2 +- .../org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java | 2 +- .../thingsboard/server/dao/sql/tenant/TenantRepository.java | 2 +- .../thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java | 2 +- .../java/org/thingsboard/server/dao/sql/user/JpaUserDao.java | 2 +- .../server/dao/sql/user/UserCredentialsRepository.java | 2 +- .../org/thingsboard/server/dao/sql/user/UserRepository.java | 2 +- .../thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java | 2 +- .../thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java | 2 +- .../server/dao/sql/widget/WidgetTypeRepository.java | 2 +- .../server/dao/sql/widget/WidgetsBundleRepository.java | 2 +- .../thingsboard/server/dao/sqlts/AbstractInsertRepository.java | 2 +- .../server/dao/sqlts/AbstractLatestInsertRepository.java | 2 +- .../thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java | 2 +- .../server/dao/sqlts/AbstractTimeseriesInsertRepository.java | 2 +- .../server/dao/sqlts/timescale/AggregationRepository.java | 2 +- .../server/dao/sqlts/timescale/TimescaleInsertRepository.java | 2 +- .../server/dao/sqlts/timescale/TimescaleTimeseriesDao.java | 2 +- .../server/dao/sqlts/timescale/TsKvTimescaleRepository.java | 2 +- .../server/dao/sqlts/ts/HsqlLatestInsertRepository.java | 2 +- .../server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java | 2 +- .../org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java | 2 +- .../server/dao/sqlts/ts/PsqlLatestInsertRepository.java | 2 +- .../server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java | 2 +- .../thingsboard/server/dao/sqlts/ts/TsKvLatestRepository.java | 2 +- .../org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java | 2 +- .../org/thingsboard/server/dao/tenant/CassandraTenantDao.java | 2 +- .../main/java/org/thingsboard/server/dao/tenant/TenantDao.java | 2 +- .../org/thingsboard/server/dao/tenant/TenantServiceImpl.java | 2 +- .../server/dao/timeseries/AggregatePartitionsFunction.java | 2 +- .../server/dao/timeseries/BaseTimeseriesService.java | 2 +- .../server/dao/timeseries/CassandraBaseTimeseriesDao.java | 2 +- .../org/thingsboard/server/dao/timeseries/QueryCursor.java | 2 +- .../server/dao/timeseries/SimpleListenableFuture.java | 2 +- .../org/thingsboard/server/dao/timeseries/TimeseriesDao.java | 2 +- .../server/dao/timeseries/TsInsertExecutorType.java | 2 +- .../org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java | 2 +- .../org/thingsboard/server/dao/timeseries/TsPartitionDate.java | 2 +- .../server/dao/user/CassandraUserCredentialsDao.java | 2 +- .../java/org/thingsboard/server/dao/user/CassandraUserDao.java | 2 +- .../org/thingsboard/server/dao/user/UserCredentialsDao.java | 2 +- dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java | 2 +- .../java/org/thingsboard/server/dao/user/UserServiceImpl.java | 2 +- .../server/dao/util/AbstractBufferedRateExecutor.java | 2 +- .../java/org/thingsboard/server/dao/util/AsyncRateLimiter.java | 2 +- .../java/org/thingsboard/server/dao/util/AsyncTaskContext.java | 2 +- .../org/thingsboard/server/dao/util/BufferedRateExecutor.java | 2 +- .../thingsboard/server/dao/util/TenantRateLimitException.java | 2 +- .../java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java | 2 +- .../server/dao/util/mapping/AbstractJsonSqlTypeDescriptor.java | 2 +- .../org/thingsboard/server/dao/util/mapping/JacksonUtil.java | 2 +- .../server/dao/util/mapping/JsonStringSqlTypeDescriptor.java | 2 +- .../thingsboard/server/dao/util/mapping/JsonStringType.java | 2 +- .../server/dao/util/mapping/JsonTypeDescriptor.java | 2 +- .../thingsboard/server/dao/widget/CassandraWidgetTypeDao.java | 2 +- .../server/dao/widget/CassandraWidgetsBundleDao.java | 2 +- .../java/org/thingsboard/server/dao/widget/WidgetTypeDao.java | 2 +- .../thingsboard/server/dao/widget/WidgetTypeServiceImpl.java | 2 +- .../org/thingsboard/server/dao/widget/WidgetsBundleDao.java | 2 +- .../server/dao/widget/WidgetsBundleServiceImpl.java | 2 +- dao/src/main/resources/cassandra/schema-entities.cql | 2 +- dao/src/main/resources/cassandra/schema-ts.cql | 2 +- dao/src/main/resources/cassandra/system-data.cql | 2 +- dao/src/main/resources/sql/schema-entities-idx.sql | 2 +- dao/src/main/resources/sql/schema-entities.sql | 2 +- dao/src/main/resources/sql/schema-timescale-idx.sql | 2 +- dao/src/main/resources/sql/schema-timescale.sql | 2 +- dao/src/main/resources/sql/schema-ts.sql | 2 +- dao/src/main/resources/sql/system-data.sql | 2 +- .../java/org/thingsboard/server/dao/AbstractJpaDaoTest.java | 2 +- .../org/thingsboard/server/dao/CustomCassandraCQLUnit.java | 2 +- .../test/java/org/thingsboard/server/dao/CustomSqlUnit.java | 2 +- .../test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java | 2 +- .../java/org/thingsboard/server/dao/JpaDbunitTestConfig.java | 2 +- .../org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java | 2 +- .../org/thingsboard/server/dao/SqlDaoServiceTestSuite.java | 2 +- .../server/dao/nosql/RateLimitedResultSetFutureTest.java | 2 +- .../thingsboard/server/dao/service/AbstractServiceTest.java | 2 +- .../server/dao/service/BaseAdminSettingsServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseAlarmServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseAssetServiceTest.java | 2 +- .../server/dao/service/BaseCustomerServiceTest.java | 2 +- .../server/dao/service/BaseDashboardServiceTest.java | 2 +- .../server/dao/service/BaseDeviceCredentialsCacheTest.java | 2 +- .../server/dao/service/BaseDeviceCredentialsServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseDeviceServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseRelationCacheTest.java | 2 +- .../server/dao/service/BaseRelationServiceTest.java | 2 +- .../server/dao/service/BaseRuleChainServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseTenantServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseUserServiceTest.java | 2 +- .../server/dao/service/BaseWidgetTypeServiceTest.java | 2 +- .../server/dao/service/BaseWidgetsBundleServiceTest.java | 2 +- .../java/org/thingsboard/server/dao/service/DaoNoSqlTest.java | 2 +- .../java/org/thingsboard/server/dao/service/DaoSqlTest.java | 2 +- .../dao/service/attributes/BaseAttributesServiceTest.java | 2 +- .../service/attributes/nosql/AttributesServiceNoSqlTest.java | 2 +- .../dao/service/attributes/sql/AttributesServiceSqlTest.java | 2 +- .../server/dao/service/event/BaseEventServiceTest.java | 2 +- .../server/dao/service/event/nosql/EventServiceNoSqlTest.java | 2 +- .../server/dao/service/event/sql/EventServiceSqlTest.java | 2 +- .../dao/service/nosql/AdminSettingsServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/AlarmServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/AssetServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/CustomerServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/DashboardServiceNoSqlTest.java | 2 +- .../service/nosql/DeviceCredentialCacheServiceNoSqlTest.java | 2 +- .../dao/service/nosql/DeviceCredentialServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/DeviceServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/RelationCacheNoSqlTest.java | 2 +- .../server/dao/service/nosql/RelationServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/RuleChainServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/TenantServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/UserServiceNoSqlTest.java | 2 +- .../server/dao/service/nosql/WidgetTypeServiceNoSqlTest.java | 2 +- .../dao/service/nosql/WidgetsBundleServiceNoSqlTest.java | 2 +- .../server/dao/service/sql/AdminSettingsServiceSqlTest.java | 2 +- .../server/dao/service/sql/AlarmServiceSqlTest.java | 2 +- .../server/dao/service/sql/AssetServiceSqlTest.java | 2 +- .../server/dao/service/sql/CustomerServiceSqlTest.java | 2 +- .../server/dao/service/sql/DashboardServiceSqlTest.java | 2 +- .../dao/service/sql/DeviceCredentialsCacheServiceSqlTest.java | 2 +- .../dao/service/sql/DeviceCredentialsServiceSqlTest.java | 2 +- .../server/dao/service/sql/DeviceServiceSqlTest.java | 2 +- .../server/dao/service/sql/RelationCacheSqlTest.java | 2 +- .../server/dao/service/sql/RelationServiceSqlTest.java | 2 +- .../server/dao/service/sql/RuleChainServiceSqlTest.java | 2 +- .../server/dao/service/sql/TenantServiceSqlTest.java | 2 +- .../thingsboard/server/dao/service/sql/UserServiceSqlTest.java | 2 +- .../server/dao/service/sql/WidgetTypeServiceSqlTest.java | 2 +- .../server/dao/service/sql/WidgetsBundleServiceSqlTest.java | 2 +- .../dao/service/timeseries/BaseTimeseriesServiceTest.java | 2 +- .../service/timeseries/nosql/TimeseriesServiceNoSqlTest.java | 2 +- .../dao/service/timeseries/sql/TimeseriesServiceSqlTest.java | 2 +- .../org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java | 2 +- .../org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java | 2 +- .../thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java | 2 +- .../dao/sql/component/JpaBaseComponentDescriptorDaoTest.java | 2 +- .../server/dao/sql/customer/JpaCustomerDaoTest.java | 2 +- .../server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java | 2 +- .../server/dao/sql/device/JpaDeviceCredentialsDaoTest.java | 2 +- .../thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java | 2 +- .../thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java | 2 +- .../thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java | 2 +- .../server/dao/sql/user/JpaUserCredentialsDaoTest.java | 2 +- .../org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java | 2 +- .../server/dao/sql/widget/JpaWidgetTypeDaoTest.java | 2 +- .../server/dao/sql/widget/JpaWidgetsBundleDaoTest.java | 2 +- docker/compose-utils.sh | 2 +- docker/docker-compose.cassandra.yml | 2 +- docker/docker-compose.postgres.volumes.yml | 2 +- docker/docker-compose.postgres.yml | 2 +- docker/docker-compose.yml | 2 +- docker/docker-install-tb.sh | 2 +- docker/docker-remove-services.sh | 2 +- docker/docker-start-services.sh | 2 +- docker/docker-stop-services.sh | 2 +- docker/docker-update-service.sh | 2 +- docker/docker-upgrade-tb.sh | 2 +- docker/tb-transports/coap/conf/logback.xml | 2 +- docker/tb-transports/coap/conf/tb-coap-transport.conf | 2 +- docker/tb-transports/http/conf/logback.xml | 2 +- docker/tb-transports/http/conf/tb-http-transport.conf | 2 +- docker/tb-transports/mqtt/conf/logback.xml | 2 +- docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf | 2 +- k8s/cassandra.yml | 2 +- k8s/database-setup.yml | 2 +- k8s/k8s-delete-all.sh | 2 +- k8s/k8s-delete-resources.sh | 2 +- k8s/k8s-deploy-resources.sh | 2 +- k8s/k8s-install-tb.sh | 2 +- k8s/k8s-upgrade-tb.sh | 2 +- k8s/postgres.yml | 2 +- k8s/tb-coap-transport-configmap.yml | 2 +- k8s/tb-http-transport-configmap.yml | 2 +- k8s/tb-mqtt-transport-configmap.yml | 2 +- k8s/tb-namespace.yml | 2 +- k8s/tb-node-cassandra-configmap.yml | 2 +- k8s/tb-node-configmap.yml | 2 +- k8s/tb-node-postgres-configmap.yml | 2 +- k8s/thingsboard.yml | 2 +- license-header-template.txt | 2 +- msa/black-box-tests/pom.xml | 2 +- .../java/org/thingsboard/server/msa/AbstractContainerTest.java | 2 +- .../java/org/thingsboard/server/msa/ContainerTestSuite.java | 2 +- .../java/org/thingsboard/server/msa/DockerComposeExecutor.java | 2 +- .../org/thingsboard/server/msa/ThingsBoardDbInstaller.java | 2 +- .../src/test/java/org/thingsboard/server/msa/WsClient.java | 2 +- .../thingsboard/server/msa/connectivity/HttpClientTest.java | 2 +- .../thingsboard/server/msa/connectivity/MqttClientTest.java | 2 +- .../org/thingsboard/server/msa/mapper/AttributesResponse.java | 2 +- .../org/thingsboard/server/msa/mapper/WsTelemetryResponse.java | 2 +- msa/js-executor/api/jsExecutor.js | 2 +- msa/js-executor/api/jsInvokeMessageProcessor.js | 2 +- msa/js-executor/api/utils.js | 2 +- msa/js-executor/build.gradle | 2 +- msa/js-executor/config/custom-environment-variables.yml | 2 +- msa/js-executor/config/default.yml | 2 +- msa/js-executor/config/logger.js | 2 +- msa/js-executor/config/tb-js-executor.conf | 2 +- msa/js-executor/docker/Dockerfile | 2 +- msa/js-executor/docker/start-js-executor.sh | 2 +- msa/js-executor/install.js | 2 +- msa/js-executor/pom.xml | 2 +- msa/js-executor/server.js | 3 +-- msa/js-executor/src/main/assembly/windows.xml | 2 +- msa/pom.xml | 2 +- msa/tb-node/docker/Dockerfile | 2 +- msa/tb-node/docker/start-tb-node.sh | 2 +- msa/tb-node/pom.xml | 2 +- msa/tb/docker-cassandra/Dockerfile | 2 +- msa/tb/docker-cassandra/start-db.sh | 2 +- msa/tb/docker-cassandra/stop-db.sh | 2 +- msa/tb/docker-postgres/Dockerfile | 2 +- msa/tb/docker-postgres/start-db.sh | 2 +- msa/tb/docker-postgres/stop-db.sh | 2 +- msa/tb/docker-tb/Dockerfile | 2 +- msa/tb/docker-tb/start-db.sh | 2 +- msa/tb/docker-tb/stop-db.sh | 2 +- msa/tb/docker/install-tb.sh | 2 +- msa/tb/docker/logback.xml | 2 +- msa/tb/docker/start-tb.sh | 2 +- msa/tb/docker/thingsboard.conf | 2 +- msa/tb/docker/upgrade-tb.sh | 2 +- msa/tb/pom.xml | 2 +- msa/transport/coap/docker/Dockerfile | 2 +- msa/transport/coap/docker/start-tb-coap-transport.sh | 2 +- msa/transport/coap/pom.xml | 2 +- msa/transport/http/docker/Dockerfile | 2 +- msa/transport/http/docker/start-tb-http-transport.sh | 2 +- msa/transport/http/pom.xml | 2 +- msa/transport/mqtt/docker/Dockerfile | 2 +- msa/transport/mqtt/docker/start-tb-mqtt-transport.sh | 2 +- msa/transport/mqtt/pom.xml | 2 +- msa/transport/pom.xml | 2 +- msa/web-ui/build.gradle | 2 +- msa/web-ui/config/custom-environment-variables.yml | 2 +- msa/web-ui/config/default.yml | 2 +- msa/web-ui/config/logger.js | 2 +- msa/web-ui/config/tb-web-ui.conf | 2 +- msa/web-ui/docker/Dockerfile | 2 +- msa/web-ui/docker/start-web-ui.sh | 2 +- msa/web-ui/install.js | 2 +- msa/web-ui/pom.xml | 2 +- msa/web-ui/server.js | 2 +- msa/web-ui/src/main/assembly/windows.xml | 2 +- netty-mqtt/pom.xml | 2 +- .../main/java/org/thingsboard/mqtt/ChannelClosedException.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java | 2 +- netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClient.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttClientCallback.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttClientConfig.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttClientImpl.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttConnectResult.java | 2 +- netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttHandler.java | 2 +- .../java/org/thingsboard/mqtt/MqttIncomingQos2Publish.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttLastWill.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttPendingPublish.java | 2 +- .../java/org/thingsboard/mqtt/MqttPendingSubscription.java | 2 +- .../java/org/thingsboard/mqtt/MqttPendingUnsubscription.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttPingHandler.java | 2 +- .../src/main/java/org/thingsboard/mqtt/MqttSubscription.java | 2 +- .../main/java/org/thingsboard/mqtt/RetransmissionHandler.java | 2 +- pom.xml | 2 +- rule-engine/pom.xml | 2 +- rule-engine/rule-engine-api/pom.xml | 2 +- .../thingsboard/rule/engine/api/EmptyNodeConfiguration.java | 2 +- .../main/java/org/thingsboard/rule/engine/api/MailService.java | 2 +- .../org/thingsboard/rule/engine/api/NodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/api/NodeDefinition.java | 2 +- .../main/java/org/thingsboard/rule/engine/api/RpcError.java | 2 +- .../rule/engine/api/RuleChainTransactionService.java | 2 +- .../rule/engine/api/RuleEngineDeviceRpcRequest.java | 2 +- .../rule/engine/api/RuleEngineDeviceRpcResponse.java | 2 +- .../org/thingsboard/rule/engine/api/RuleEngineRpcService.java | 2 +- .../rule/engine/api/RuleEngineTelemetryService.java | 2 +- .../main/java/org/thingsboard/rule/engine/api/RuleNode.java | 2 +- .../java/org/thingsboard/rule/engine/api/ScriptEngine.java | 2 +- .../main/java/org/thingsboard/rule/engine/api/TbContext.java | 2 +- .../src/main/java/org/thingsboard/rule/engine/api/TbNode.java | 2 +- .../org/thingsboard/rule/engine/api/TbNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/api/TbNodeException.java | 2 +- .../main/java/org/thingsboard/rule/engine/api/TbNodeState.java | 2 +- .../java/org/thingsboard/rule/engine/api/TbRelationTypes.java | 2 +- .../org/thingsboard/rule/engine/api/msg/DeviceAttributes.java | 2 +- .../engine/api/msg/DeviceAttributesEventNotificationMsg.java | 2 +- .../engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java | 2 +- .../org/thingsboard/rule/engine/api/msg/DeviceMetaData.java | 2 +- .../rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java | 2 +- .../rule/engine/api/msg/ToDeviceActorNotificationMsg.java | 2 +- .../java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java | 2 +- rule-engine/rule-engine-components/pom.xml | 2 +- .../thingsboard/rule/engine/action/TbAbstractAlarmNode.java | 2 +- .../rule/engine/action/TbAbstractAlarmNodeConfiguration.java | 2 +- .../rule/engine/action/TbAbstractCustomerActionNode.java | 2 +- .../action/TbAbstractCustomerActionNodeConfiguration.java | 2 +- .../rule/engine/action/TbAbstractRelationActionNode.java | 2 +- .../action/TbAbstractRelationActionNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/action/TbAssignToCustomerNode.java | 2 +- .../engine/action/TbAssignToCustomerNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/action/TbClearAlarmNode.java | 2 +- .../rule/engine/action/TbClearAlarmNodeConfiguration.java | 2 +- .../rule/engine/action/TbCopyAttributesToEntityViewNode.java | 2 +- .../org/thingsboard/rule/engine/action/TbCreateAlarmNode.java | 2 +- .../rule/engine/action/TbCreateAlarmNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/action/TbCreateRelationNode.java | 2 +- .../rule/engine/action/TbCreateRelationNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/action/TbDeleteRelationNode.java | 2 +- .../rule/engine/action/TbDeleteRelationNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/action/TbLogNode.java | 2 +- .../thingsboard/rule/engine/action/TbLogNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/action/TbMsgCountNode.java | 2 +- .../rule/engine/action/TbMsgCountNodeConfiguration.java | 2 +- .../rule/engine/action/TbSaveToCustomCassandraTableNode.java | 2 +- .../action/TbSaveToCustomCassandraTableNodeConfiguration.java | 2 +- .../rule/engine/action/TbUnassignFromCustomerNode.java | 2 +- .../engine/action/TbUnassignFromCustomerNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java | 2 +- .../rule/engine/aws/sns/TbSnsNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java | 2 +- .../rule/engine/aws/sqs/TbSqsNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/data/DeviceRelationsQuery.java | 2 +- .../java/org/thingsboard/rule/engine/data/RelationsQuery.java | 2 +- .../org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java | 2 +- .../rule/engine/delay/TbMsgDelayNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/filter/TbCheckMessageNode.java | 2 +- .../rule/engine/filter/TbCheckMessageNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/filter/TbCheckRelationNode.java | 2 +- .../rule/engine/filter/TbCheckRelationNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/filter/TbJsFilterNode.java | 2 +- .../rule/engine/filter/TbJsFilterNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/filter/TbJsSwitchNode.java | 2 +- .../rule/engine/filter/TbJsSwitchNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java | 2 +- .../rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java | 2 +- .../rule/engine/filter/TbOriginatorTypeFilterNode.java | 2 +- .../engine/filter/TbOriginatorTypeFilterNodeConfiguration.java | 2 +- .../rule/engine/filter/TbOriginatorTypeSwitchNode.java | 2 +- .../org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java | 2 +- .../rule/engine/gcp/pubsub/TbPubSubNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/geo/AbstractGeofencingNode.java | 2 +- .../main/java/org/thingsboard/rule/engine/geo/Coordinates.java | 2 +- .../org/thingsboard/rule/engine/geo/EntityGeofencingState.java | 2 +- .../src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java | 2 +- .../main/java/org/thingsboard/rule/engine/geo/Perimeter.java | 2 +- .../java/org/thingsboard/rule/engine/geo/PerimeterType.java | 2 +- .../main/java/org/thingsboard/rule/engine/geo/RangeUnit.java | 2 +- .../thingsboard/rule/engine/geo/TbGpsGeofencingActionNode.java | 2 +- .../engine/geo/TbGpsGeofencingActionNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/geo/TbGpsGeofencingFilterNode.java | 2 +- .../engine/geo/TbGpsGeofencingFilterNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java | 2 +- .../rule/engine/kafka/TbKafkaNodeConfiguration.java | 2 +- .../main/java/org/thingsboard/rule/engine/mail/EmailPojo.java | 2 +- .../org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java | 2 +- .../rule/engine/mail/TbMsgToEmailNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java | 2 +- .../rule/engine/mail/TbSendEmailNodeConfiguration.java | 2 +- .../rule/engine/metadata/TbAbstractGetAttributesNode.java | 2 +- .../rule/engine/metadata/TbAbstractGetEntityDetailsNode.java | 2 +- .../metadata/TbAbstractGetEntityDetailsNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java | 2 +- .../thingsboard/rule/engine/metadata/TbGetAttributesNode.java | 2 +- .../rule/engine/metadata/TbGetAttributesNodeConfiguration.java | 2 +- .../rule/engine/metadata/TbGetCustomerAttributeNode.java | 2 +- .../rule/engine/metadata/TbGetCustomerDetailsNode.java | 2 +- .../engine/metadata/TbGetCustomerDetailsNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/metadata/TbGetDeviceAttrNode.java | 2 +- .../rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java | 2 +- .../rule/engine/metadata/TbGetEntityAttrNodeConfiguration.java | 2 +- .../engine/metadata/TbGetOriginatorFieldsConfiguration.java | 2 +- .../rule/engine/metadata/TbGetOriginatorFieldsNode.java | 2 +- .../engine/metadata/TbGetRelatedAttrNodeConfiguration.java | 2 +- .../rule/engine/metadata/TbGetRelatedAttributeNode.java | 2 +- .../thingsboard/rule/engine/metadata/TbGetTelemetryNode.java | 2 +- .../rule/engine/metadata/TbGetTelemetryNodeConfiguration.java | 2 +- .../rule/engine/metadata/TbGetTenantAttributeNode.java | 2 +- .../rule/engine/metadata/TbGetTenantDetailsNode.java | 2 +- .../engine/metadata/TbGetTenantDetailsNodeConfiguration.java | 2 +- .../main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java | 2 +- .../thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java | 2 +- .../rule/engine/mqtt/credentials/AnonymousCredentials.java | 2 +- .../rule/engine/mqtt/credentials/BasicCredentials.java | 2 +- .../rule/engine/mqtt/credentials/CertPemClientCredentials.java | 2 +- .../rule/engine/mqtt/credentials/MqttClientCredentials.java | 2 +- .../org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java | 2 +- .../rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java | 2 +- .../java/org/thingsboard/rule/engine/rest/TbHttpClient.java | 2 +- .../thingsboard/rule/engine/rest/TbRedisQueueProcessor.java | 2 +- .../org/thingsboard/rule/engine/rest/TbRestApiCallNode.java | 2 +- .../rule/engine/rest/TbRestApiCallNodeConfiguration.java | 2 +- .../org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java | 2 +- .../org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java | 2 +- .../rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java | 2 +- .../rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java | 2 +- .../engine/telemetry/TbMsgAttributesNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNodeConfiguration.java | 2 +- .../rule/engine/telemetry/TelemetryNodeCallback.java | 2 +- .../rule/engine/transaction/TbSynchronizationBeginNode.java | 2 +- .../rule/engine/transaction/TbSynchronizationEndNode.java | 2 +- .../rule/engine/transform/TbAbstractTransformNode.java | 2 +- .../rule/engine/transform/TbChangeOriginatorNode.java | 2 +- .../engine/transform/TbChangeOriginatorNodeConfiguration.java | 2 +- .../thingsboard/rule/engine/transform/TbTransformMsgNode.java | 2 +- .../rule/engine/transform/TbTransformMsgNodeConfiguration.java | 2 +- .../rule/engine/transform/TbTransformNodeConfiguration.java | 2 +- .../rule/engine/util/EntitiesAlarmOriginatorIdAsyncLoader.java | 2 +- .../rule/engine/util/EntitiesCustomerIdAsyncLoader.java | 2 +- .../rule/engine/util/EntitiesFieldsAsyncLoader.java | 2 +- .../rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java | 2 +- .../rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java | 2 +- .../rule/engine/util/EntitiesTenantIdAsyncLoader.java | 2 +- .../java/org/thingsboard/rule/engine/util/EntityContainer.java | 2 +- .../java/org/thingsboard/rule/engine/util/EntityDetails.java | 2 +- .../org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 2 +- .../org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java | 2 +- .../org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java | 2 +- .../org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java | 2 +- .../rule/engine/metadata/TbGetCustomerAttributeNodeTest.java | 2 +- .../rule/engine/transform/TbChangeOriginatorNodeTest.java | 2 +- .../rule/engine/transform/TbTransformMsgNodeTest.java | 2 +- tools/pom.xml | 2 +- .../main/java/org/thingsboard/client/tools/MqttSslClient.java | 2 +- .../src/main/java/org/thingsboard/client/tools/RestClient.java | 2 +- .../org/thingsboard/client/tools/migrator/MigratorTool.java | 2 +- .../thingsboard/client/tools/migrator/PgCaLatestMigrator.java | 2 +- .../tools/migrator/PostgresToCassandraTelemetryMigrator.java | 2 +- .../org/thingsboard/client/tools/migrator/WriterBuilder.java | 2 +- tools/src/main/python/mqtt-send-telemetry.py | 2 +- tools/src/main/python/one-way-ssl-mqtt-client.py | 2 +- tools/src/main/python/simple-mqtt-client.py | 2 +- tools/src/main/python/two-way-ssl-mqtt-client.py | 2 +- tools/src/main/shell/client.keygen.sh | 2 +- tools/src/main/shell/server.keygen.sh | 2 +- transport/coap/build.gradle | 2 +- transport/coap/pom.xml | 2 +- transport/coap/src/main/assembly/windows.xml | 2 +- transport/coap/src/main/conf/logback.xml | 2 +- transport/coap/src/main/conf/tb-coap-transport.conf | 2 +- .../server/coap/ThingsboardCoapTransportApplication.java | 2 +- transport/coap/src/main/resources/logback.xml | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 2 +- transport/http/build.gradle | 2 +- transport/http/pom.xml | 2 +- transport/http/src/main/assembly/windows.xml | 2 +- transport/http/src/main/conf/logback.xml | 2 +- transport/http/src/main/conf/tb-http-transport.conf | 2 +- .../server/http/ThingsboardHttpTransportApplication.java | 2 +- transport/http/src/main/resources/logback.xml | 2 +- transport/http/src/main/resources/tb-http-transport.yml | 2 +- transport/mqtt/build.gradle | 2 +- transport/mqtt/pom.xml | 2 +- transport/mqtt/src/main/assembly/windows.xml | 2 +- transport/mqtt/src/main/conf/logback.xml | 2 +- transport/mqtt/src/main/conf/tb-mqtt-transport.conf | 2 +- .../server/mqtt/ThingsboardMqttTransportApplication.java | 2 +- transport/mqtt/src/main/resources/logback.xml | 2 +- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 2 +- transport/pom.xml | 2 +- ui/pom.xml | 2 +- ui/server.js | 2 +- ui/src/app/admin/admin.controller.js | 3 +-- ui/src/app/admin/admin.routes.js | 2 +- ui/src/app/admin/general-settings.tpl.html | 2 +- ui/src/app/admin/index.js | 2 +- ui/src/app/admin/outgoing-mail-settings.tpl.html | 2 +- ui/src/app/admin/security-settings.controller.js | 3 +-- ui/src/app/admin/security-settings.tpl.html | 2 +- ui/src/app/admin/settings-card.scss | 3 +-- ui/src/app/alarm/alarm-details-dialog.controller.js | 2 +- ui/src/app/alarm/alarm-details-dialog.scss | 2 +- ui/src/app/alarm/alarm-details-dialog.tpl.html | 2 +- ui/src/app/alarm/alarm-header.directive.js | 2 +- ui/src/app/alarm/alarm-header.tpl.html | 2 +- ui/src/app/alarm/alarm-row.directive.js | 2 +- ui/src/app/alarm/alarm-row.tpl.html | 2 +- ui/src/app/alarm/alarm-table.directive.js | 2 +- ui/src/app/alarm/alarm-table.tpl.html | 2 +- ui/src/app/alarm/alarm.scss | 2 +- ui/src/app/alarm/index.js | 2 +- ui/src/app/api/admin.service.js | 2 +- ui/src/app/api/alarm.service.js | 2 +- ui/src/app/api/alias-controller.js | 2 +- ui/src/app/api/asset.service.js | 2 +- ui/src/app/api/attribute.service.js | 2 +- ui/src/app/api/audit-log.service.js | 2 +- ui/src/app/api/component-descriptor.service.js | 2 +- ui/src/app/api/customer.service.js | 2 +- ui/src/app/api/dashboard.service.js | 2 +- ui/src/app/api/data-aggregator.js | 2 +- ui/src/app/api/datasource.service.js | 2 +- ui/src/app/api/device.service.js | 2 +- ui/src/app/api/entity-relation.service.js | 2 +- ui/src/app/api/entity-view.service.js | 2 +- ui/src/app/api/entity.service.js | 2 +- ui/src/app/api/event.service.js | 2 +- ui/src/app/api/login.service.js | 2 +- ui/src/app/api/rule-chain.service.js | 2 +- ui/src/app/api/subscription.js | 2 +- ui/src/app/api/telemetry-websocket.service.js | 2 +- ui/src/app/api/tenant.service.js | 2 +- ui/src/app/api/time.service.js | 2 +- ui/src/app/api/user.service.js | 2 +- ui/src/app/api/widget.service.js | 2 +- ui/src/app/app.config.js | 2 +- ui/src/app/app.js | 2 +- ui/src/app/app.run.js | 2 +- ui/src/app/asset/add-asset.tpl.html | 2 +- ui/src/app/asset/add-assets-to-customer.controller.js | 2 +- ui/src/app/asset/add-assets-to-customer.tpl.html | 2 +- ui/src/app/asset/asset-card.tpl.html | 2 +- ui/src/app/asset/asset-fieldset.tpl.html | 2 +- ui/src/app/asset/asset.controller.js | 2 +- ui/src/app/asset/asset.directive.js | 2 +- ui/src/app/asset/asset.routes.js | 2 +- ui/src/app/asset/assets.tpl.html | 2 +- ui/src/app/asset/assign-to-customer.controller.js | 2 +- ui/src/app/asset/assign-to-customer.tpl.html | 2 +- ui/src/app/asset/index.js | 2 +- ui/src/app/audit/audit-log-details-dialog.controller.js | 2 +- ui/src/app/audit/audit-log-details-dialog.scss | 2 +- ui/src/app/audit/audit-log-details-dialog.tpl.html | 2 +- ui/src/app/audit/audit-log-header.directive.js | 2 +- ui/src/app/audit/audit-log-header.tpl.html | 2 +- ui/src/app/audit/audit-log-row.directive.js | 2 +- ui/src/app/audit/audit-log-row.tpl.html | 2 +- ui/src/app/audit/audit-log-table.directive.js | 2 +- ui/src/app/audit/audit-log-table.tpl.html | 2 +- ui/src/app/audit/audit-log.routes.js | 2 +- ui/src/app/audit/audit-log.scss | 2 +- ui/src/app/audit/audit-logs.controller.js | 2 +- ui/src/app/audit/audit-logs.tpl.html | 2 +- ui/src/app/audit/index.js | 2 +- ui/src/app/common/dashboard-utils.service.js | 2 +- ui/src/app/common/raf.provider.js | 2 +- ui/src/app/common/thirdparty-fix.js | 2 +- ui/src/app/common/types.constant.js | 2 +- ui/src/app/common/utf8-support.js | 2 +- ui/src/app/common/utils.service.js | 2 +- ui/src/app/components/ace-editor-fix.js | 2 +- ui/src/app/components/circular-progress.directive.js | 2 +- ui/src/app/components/confirm-on-exit.directive.js | 2 +- ui/src/app/components/contact-short.filter.js | 2 +- ui/src/app/components/contact.directive.js | 2 +- ui/src/app/components/contact.tpl.html | 2 +- ui/src/app/components/dashboard-autocomplete.directive.js | 2 +- ui/src/app/components/dashboard-autocomplete.scss | 2 +- ui/src/app/components/dashboard-autocomplete.tpl.html | 2 +- ui/src/app/components/dashboard-select-panel.controller.js | 2 +- ui/src/app/components/dashboard-select-panel.tpl.html | 2 +- ui/src/app/components/dashboard-select.directive.js | 2 +- ui/src/app/components/dashboard-select.scss | 2 +- ui/src/app/components/dashboard-select.tpl.html | 2 +- ui/src/app/components/dashboard.directive.js | 2 +- ui/src/app/components/dashboard.scss | 2 +- ui/src/app/components/dashboard.tpl.html | 2 +- ui/src/app/components/datakey-config-dialog.controller.js | 2 +- ui/src/app/components/datakey-config-dialog.tpl.html | 2 +- ui/src/app/components/datakey-config.directive.js | 2 +- ui/src/app/components/datakey-config.scss | 2 +- ui/src/app/components/datakey-config.tpl.html | 2 +- ui/src/app/components/datasource-entity.directive.js | 2 +- ui/src/app/components/datasource-entity.scss | 2 +- ui/src/app/components/datasource-entity.tpl.html | 2 +- ui/src/app/components/datasource-func.directive.js | 2 +- ui/src/app/components/datasource-func.scss | 2 +- ui/src/app/components/datasource-func.tpl.html | 2 +- ui/src/app/components/datasource.directive.js | 2 +- ui/src/app/components/datasource.scss | 2 +- ui/src/app/components/datasource.tpl.html | 2 +- ui/src/app/components/datetime-period.directive.js | 2 +- ui/src/app/components/datetime-period.scss | 2 +- ui/src/app/components/datetime-period.tpl.html | 2 +- ui/src/app/components/details-sidenav.directive.js | 2 +- ui/src/app/components/details-sidenav.scss | 2 +- ui/src/app/components/details-sidenav.tpl.html | 2 +- ui/src/app/components/entity-alias-select.directive.js | 2 +- ui/src/app/components/entity-alias-select.scss | 2 +- ui/src/app/components/entity-alias-select.tpl.html | 2 +- ui/src/app/components/expand-fullscreen.directive.js | 2 +- ui/src/app/components/expand-fullscreen.scss | 2 +- ui/src/app/components/finish-render.directive.js | 2 +- ui/src/app/components/grid.directive.js | 2 +- ui/src/app/components/grid.scss | 2 +- ui/src/app/components/grid.tpl.html | 2 +- ui/src/app/components/js-func.directive.js | 2 +- ui/src/app/components/js-func.scss | 2 +- ui/src/app/components/js-func.tpl.html | 2 +- ui/src/app/components/json-content.directive.js | 2 +- ui/src/app/components/json-content.scss | 2 +- ui/src/app/components/json-content.tpl.html | 2 +- ui/src/app/components/json-form.directive.js | 2 +- ui/src/app/components/json-form.scss | 2 +- ui/src/app/components/json-form.tpl.html | 2 +- ui/src/app/components/json-object-edit.directive.js | 2 +- ui/src/app/components/json-object-edit.scss | 2 +- ui/src/app/components/json-object-edit.tpl.html | 2 +- ui/src/app/components/keyboard-shortcut.filter.js | 2 +- ui/src/app/components/kv-map.directive.js | 2 +- ui/src/app/components/kv-map.scss | 2 +- ui/src/app/components/kv-map.tpl.html | 2 +- ui/src/app/components/led-light.directive.js | 2 +- ui/src/app/components/legend-config-button.tpl.html | 2 +- ui/src/app/components/legend-config-panel.controller.js | 2 +- ui/src/app/components/legend-config-panel.tpl.html | 2 +- ui/src/app/components/legend-config.directive.js | 2 +- ui/src/app/components/legend-config.scss | 2 +- ui/src/app/components/legend.directive.js | 2 +- ui/src/app/components/legend.scss | 2 +- ui/src/app/components/legend.tpl.html | 2 +- ui/src/app/components/material-icon-select.directive.js | 2 +- ui/src/app/components/material-icon-select.scss | 2 +- ui/src/app/components/material-icon-select.tpl.html | 2 +- ui/src/app/components/material-icons-dialog.controller.js | 2 +- ui/src/app/components/material-icons-dialog.scss | 2 +- ui/src/app/components/material-icons-dialog.tpl.html | 2 +- ui/src/app/components/md-chip-draggable.directive.js | 3 +-- ui/src/app/components/md-chip-draggable.scss | 2 +- ui/src/app/components/menu-link.directive.js | 2 +- ui/src/app/components/menu-link.scss | 2 +- ui/src/app/components/menu-link.tpl.html | 2 +- ui/src/app/components/menu-toggle.tpl.html | 2 +- ui/src/app/components/mousepoint-menu.directive.js | 2 +- ui/src/app/components/nav-tree.directive.js | 2 +- ui/src/app/components/nav-tree.scss | 2 +- ui/src/app/components/nav-tree.tpl.html | 2 +- ui/src/app/components/no-animate.directive.js | 2 +- ui/src/app/components/react/json-form-ace-editor.jsx | 2 +- ui/src/app/components/react/json-form-ace-editor.scss | 3 +-- ui/src/app/components/react/json-form-array.jsx | 2 +- ui/src/app/components/react/json-form-base-component.jsx | 2 +- ui/src/app/components/react/json-form-checkbox.jsx | 2 +- ui/src/app/components/react/json-form-color.jsx | 2 +- ui/src/app/components/react/json-form-color.scss | 2 +- ui/src/app/components/react/json-form-css.jsx | 2 +- ui/src/app/components/react/json-form-date.jsx | 2 +- ui/src/app/components/react/json-form-fieldset.jsx | 2 +- ui/src/app/components/react/json-form-html.jsx | 2 +- ui/src/app/components/react/json-form-icon.jsx | 2 +- ui/src/app/components/react/json-form-image.jsx | 2 +- ui/src/app/components/react/json-form-image.scss | 2 +- ui/src/app/components/react/json-form-javascript.jsx | 2 +- ui/src/app/components/react/json-form-json.jsx | 2 +- ui/src/app/components/react/json-form-number.jsx | 2 +- ui/src/app/components/react/json-form-rc-select.jsx | 2 +- ui/src/app/components/react/json-form-react.jsx | 2 +- ui/src/app/components/react/json-form-schema-form.jsx | 2 +- ui/src/app/components/react/json-form-text.jsx | 2 +- ui/src/app/components/react/json-form.scss | 2 +- ui/src/app/components/react/styles/thingsboardTheme.js | 2 +- ui/src/app/components/related-entity-autocomplete.directive.js | 2 +- ui/src/app/components/related-entity-autocomplete.scss | 2 +- ui/src/app/components/related-entity-autocomplete.tpl.html | 2 +- ui/src/app/components/scope-element.directive.js | 2 +- ui/src/app/components/side-menu.directive.js | 2 +- ui/src/app/components/side-menu.scss | 2 +- ui/src/app/components/side-menu.tpl.html | 2 +- ui/src/app/components/socialshare-panel.directive.js | 2 +- ui/src/app/components/socialshare-panel.tpl.html | 2 +- ui/src/app/components/tb-event-directives.js | 2 +- ui/src/app/components/timeinterval.directive.js | 2 +- ui/src/app/components/timeinterval.scss | 2 +- ui/src/app/components/timeinterval.tpl.html | 2 +- ui/src/app/components/timewindow-button.tpl.html | 2 +- ui/src/app/components/timewindow-panel.controller.js | 2 +- ui/src/app/components/timewindow-panel.tpl.html | 2 +- ui/src/app/components/timewindow.directive.js | 2 +- ui/src/app/components/timewindow.scss | 2 +- ui/src/app/components/timewindow.tpl.html | 2 +- ui/src/app/components/truncate.filter.js | 2 +- .../widget/action/custom-action-pretty-editor.directive.js | 2 +- .../components/widget/action/custom-action-pretty-editor.scss | 2 +- .../widget/action/custom-action-pretty-editor.tpl.html | 2 +- .../widget/action/manage-widget-actions.directive.js | 2 +- ui/src/app/components/widget/action/manage-widget-actions.scss | 2 +- .../components/widget/action/manage-widget-actions.tpl.html | 2 +- .../widget/action/widget-action-dialog.controller.js | 2 +- .../app/components/widget/action/widget-action-dialog.tpl.html | 2 +- ui/src/app/components/widget/widget-config.directive.js | 2 +- ui/src/app/components/widget/widget-config.scss | 2 +- ui/src/app/components/widget/widget-config.tpl.html | 2 +- ui/src/app/components/widget/widget.controller.js | 2 +- ui/src/app/components/widget/widget.directive.js | 2 +- ui/src/app/components/widget/widget.scss | 2 +- ui/src/app/components/widgets-bundle-select.directive.js | 2 +- ui/src/app/components/widgets-bundle-select.scss | 2 +- ui/src/app/components/widgets-bundle-select.tpl.html | 2 +- ui/src/app/customer/add-customer.tpl.html | 2 +- ui/src/app/customer/customer-card.tpl.html | 2 +- ui/src/app/customer/customer-fieldset.tpl.html | 2 +- ui/src/app/customer/customer.controller.js | 2 +- ui/src/app/customer/customer.directive.js | 2 +- ui/src/app/customer/customer.routes.js | 2 +- ui/src/app/customer/customers.tpl.html | 2 +- ui/src/app/customer/index.js | 2 +- ui/src/app/dashboard/add-dashboard.tpl.html | 2 +- ui/src/app/dashboard/add-dashboards-to-customer.controller.js | 2 +- ui/src/app/dashboard/add-dashboards-to-customer.tpl.html | 2 +- ui/src/app/dashboard/add-widget.controller.js | 2 +- ui/src/app/dashboard/add-widget.tpl.html | 2 +- ui/src/app/dashboard/dashboard-card.scss | 2 +- ui/src/app/dashboard/dashboard-card.tpl.html | 2 +- ui/src/app/dashboard/dashboard-fieldset.tpl.html | 2 +- ui/src/app/dashboard/dashboard-settings.controller.js | 2 +- ui/src/app/dashboard/dashboard-settings.scss | 2 +- ui/src/app/dashboard/dashboard-settings.tpl.html | 2 +- ui/src/app/dashboard/dashboard-toolbar.directive.js | 2 +- ui/src/app/dashboard/dashboard-toolbar.scss | 2 +- ui/src/app/dashboard/dashboard-toolbar.tpl.html | 2 +- ui/src/app/dashboard/dashboard.controller.js | 2 +- ui/src/app/dashboard/dashboard.directive.js | 2 +- ui/src/app/dashboard/dashboard.routes.js | 2 +- ui/src/app/dashboard/dashboard.scss | 2 +- ui/src/app/dashboard/dashboard.tpl.html | 2 +- ui/src/app/dashboard/dashboards.controller.js | 2 +- ui/src/app/dashboard/dashboards.tpl.html | 2 +- ui/src/app/dashboard/edit-widget.directive.js | 2 +- ui/src/app/dashboard/edit-widget.tpl.html | 2 +- ui/src/app/dashboard/index.js | 2 +- ui/src/app/dashboard/layouts/dashboard-layout.directive.js | 2 +- ui/src/app/dashboard/layouts/dashboard-layout.tpl.html | 2 +- ui/src/app/dashboard/layouts/index.js | 2 +- .../dashboard/layouts/manage-dashboard-layouts.controller.js | 2 +- ui/src/app/dashboard/layouts/manage-dashboard-layouts.tpl.html | 2 +- .../app/dashboard/layouts/select-target-layout.controller.js | 2 +- ui/src/app/dashboard/layouts/select-target-layout.tpl.html | 2 +- ui/src/app/dashboard/make-dashboard-public-dialog.tpl.html | 2 +- ui/src/app/dashboard/manage-assigned-customers.controller.js | 2 +- ui/src/app/dashboard/manage-assigned-customers.tpl.html | 2 +- .../app/dashboard/states/dashboard-state-dialog.controller.js | 2 +- ui/src/app/dashboard/states/dashboard-state-dialog.tpl.html | 2 +- ui/src/app/dashboard/states/default-state-controller.js | 2 +- ui/src/app/dashboard/states/default-state-controller.scss | 2 +- ui/src/app/dashboard/states/default-state-controller.tpl.html | 2 +- ui/src/app/dashboard/states/entity-state-controller.js | 2 +- ui/src/app/dashboard/states/entity-state-controller.scss | 2 +- ui/src/app/dashboard/states/entity-state-controller.tpl.html | 2 +- ui/src/app/dashboard/states/index.js | 2 +- .../app/dashboard/states/manage-dashboard-states.controller.js | 2 +- ui/src/app/dashboard/states/manage-dashboard-states.scss | 2 +- ui/src/app/dashboard/states/manage-dashboard-states.tpl.html | 2 +- ui/src/app/dashboard/states/select-target-state.controller.js | 2 +- ui/src/app/dashboard/states/select-target-state.tpl.html | 2 +- ui/src/app/dashboard/states/states-component.directive.js | 2 +- ui/src/app/dashboard/states/states-controller.service.js | 2 +- ui/src/app/device/add-device.tpl.html | 2 +- ui/src/app/device/add-devices-to-customer.controller.js | 2 +- ui/src/app/device/add-devices-to-customer.tpl.html | 2 +- ui/src/app/device/assign-to-customer.controller.js | 2 +- ui/src/app/device/assign-to-customer.tpl.html | 2 +- ui/src/app/device/device-card.tpl.html | 2 +- ui/src/app/device/device-credentials.controller.js | 2 +- ui/src/app/device/device-credentials.tpl.html | 2 +- ui/src/app/device/device-fieldset.tpl.html | 2 +- ui/src/app/device/device.controller.js | 2 +- ui/src/app/device/device.directive.js | 2 +- ui/src/app/device/device.routes.js | 2 +- ui/src/app/device/devices.tpl.html | 2 +- ui/src/app/device/index.js | 2 +- ui/src/app/entity-view/add-entity-view.tpl.html | 2 +- .../app/entity-view/add-entity-views-to-customer.controller.js | 2 +- ui/src/app/entity-view/add-entity-views-to-customer.tpl.html | 2 +- ui/src/app/entity-view/assign-to-customer.controller.js | 2 +- ui/src/app/entity-view/assign-to-customer.tpl.html | 2 +- ui/src/app/entity-view/entity-view-card.tpl.html | 2 +- ui/src/app/entity-view/entity-view-fieldset.tpl.html | 2 +- ui/src/app/entity-view/entity-view.controller.js | 2 +- ui/src/app/entity-view/entity-view.directive.js | 2 +- ui/src/app/entity-view/entity-view.routes.js | 2 +- ui/src/app/entity-view/entity-view.scss | 2 +- ui/src/app/entity-view/entity-views.tpl.html | 2 +- ui/src/app/entity-view/index.js | 2 +- ui/src/app/entity/alias/aliases-entity-select-button.tpl.html | 2 +- .../app/entity/alias/aliases-entity-select-panel.controller.js | 2 +- ui/src/app/entity/alias/aliases-entity-select-panel.tpl.html | 2 +- ui/src/app/entity/alias/aliases-entity-select.directive.js | 2 +- ui/src/app/entity/alias/aliases-entity-select.scss | 2 +- ui/src/app/entity/alias/entity-alias-dialog.controller.js | 2 +- ui/src/app/entity/alias/entity-alias-dialog.scss | 2 +- ui/src/app/entity/alias/entity-alias-dialog.tpl.html | 2 +- ui/src/app/entity/alias/entity-aliases.controller.js | 2 +- ui/src/app/entity/alias/entity-aliases.scss | 2 +- ui/src/app/entity/alias/entity-aliases.tpl.html | 2 +- ui/src/app/entity/attribute/add-attribute-dialog.controller.js | 2 +- ui/src/app/entity/attribute/add-attribute-dialog.tpl.html | 2 +- .../attribute/add-widget-to-dashboard-dialog.controller.js | 2 +- .../entity/attribute/add-widget-to-dashboard-dialog.tpl.html | 2 +- ui/src/app/entity/attribute/attribute-table.directive.js | 2 +- ui/src/app/entity/attribute/attribute-table.scss | 2 +- ui/src/app/entity/attribute/attribute-table.tpl.html | 2 +- ui/src/app/entity/attribute/edit-attribute-value.controller.js | 2 +- ui/src/app/entity/attribute/edit-attribute-value.tpl.html | 2 +- ui/src/app/entity/entity-autocomplete.directive.js | 2 +- ui/src/app/entity/entity-autocomplete.scss | 2 +- ui/src/app/entity/entity-autocomplete.tpl.html | 2 +- ui/src/app/entity/entity-filter-view.directive.js | 2 +- ui/src/app/entity/entity-filter-view.scss | 2 +- ui/src/app/entity/entity-filter-view.tpl.html | 2 +- ui/src/app/entity/entity-filter.directive.js | 2 +- ui/src/app/entity/entity-filter.scss | 2 +- ui/src/app/entity/entity-filter.tpl.html | 2 +- ui/src/app/entity/entity-list.directive.js | 2 +- ui/src/app/entity/entity-list.scss | 3 +-- ui/src/app/entity/entity-list.tpl.html | 2 +- ui/src/app/entity/entity-select.directive.js | 2 +- ui/src/app/entity/entity-select.scss | 3 +-- ui/src/app/entity/entity-select.tpl.html | 2 +- ui/src/app/entity/entity-subtype-autocomplete.directive.js | 2 +- ui/src/app/entity/entity-subtype-autocomplete.scss | 2 +- ui/src/app/entity/entity-subtype-autocomplete.tpl.html | 2 +- ui/src/app/entity/entity-subtype-list.directive.js | 2 +- ui/src/app/entity/entity-subtype-list.scss | 3 +-- ui/src/app/entity/entity-subtype-list.tpl.html | 2 +- ui/src/app/entity/entity-subtype-select.directive.js | 2 +- ui/src/app/entity/entity-subtype-select.scss | 2 +- ui/src/app/entity/entity-subtype-select.tpl.html | 2 +- ui/src/app/entity/entity-type-list.directive.js | 2 +- ui/src/app/entity/entity-type-list.scss | 3 +-- ui/src/app/entity/entity-type-list.tpl.html | 2 +- ui/src/app/entity/entity-type-select.directive.js | 2 +- ui/src/app/entity/entity-type-select.scss | 3 +-- ui/src/app/entity/entity-type-select.tpl.html | 2 +- ui/src/app/entity/index.js | 2 +- ui/src/app/entity/relation/relation-dialog.controller.js | 2 +- ui/src/app/entity/relation/relation-dialog.scss | 2 +- ui/src/app/entity/relation/relation-dialog.tpl.html | 2 +- ui/src/app/entity/relation/relation-filters.directive.js | 2 +- ui/src/app/entity/relation/relation-filters.scss | 2 +- ui/src/app/entity/relation/relation-filters.tpl.html | 2 +- ui/src/app/entity/relation/relation-table.directive.js | 2 +- ui/src/app/entity/relation/relation-table.scss | 2 +- ui/src/app/entity/relation/relation-table.tpl.html | 2 +- .../entity/relation/relation-type-autocomplete.directive.js | 2 +- ui/src/app/entity/relation/relation-type-autocomplete.scss | 2 +- ui/src/app/entity/relation/relation-type-autocomplete.tpl.html | 2 +- ui/src/app/event/event-content-dialog.controller.js | 2 +- ui/src/app/event/event-content-dialog.tpl.html | 2 +- ui/src/app/event/event-header-debug-rulenode.tpl.html | 2 +- ui/src/app/event/event-header-error.tpl.html | 2 +- ui/src/app/event/event-header-lc-event.tpl.html | 2 +- ui/src/app/event/event-header-stats.tpl.html | 2 +- ui/src/app/event/event-header.directive.js | 2 +- ui/src/app/event/event-row-debug-rulenode.tpl.html | 2 +- ui/src/app/event/event-row-error.tpl.html | 2 +- ui/src/app/event/event-row-lc-event.tpl.html | 2 +- ui/src/app/event/event-row-stats.tpl.html | 2 +- ui/src/app/event/event-row.directive.js | 2 +- ui/src/app/event/event-table.directive.js | 2 +- ui/src/app/event/event-table.tpl.html | 2 +- ui/src/app/event/event.scss | 2 +- ui/src/app/event/index.js | 2 +- ui/src/app/extension/extension-dialog.controller.js | 2 +- ui/src/app/extension/extension-dialog.tpl.html | 2 +- ui/src/app/extension/extension-table.directive.js | 2 +- ui/src/app/extension/extension-table.scss | 2 +- ui/src/app/extension/extension-table.tpl.html | 2 +- .../extensions-forms/extension-form-http.directive.js | 2 +- .../extension/extensions-forms/extension-form-http.tpl.html | 2 +- .../extensions-forms/extension-form-modbus.directive.js | 2 +- .../extension/extensions-forms/extension-form-modbus.tpl.html | 2 +- .../extensions-forms/extension-form-mqtt.directive.js | 2 +- .../extension/extensions-forms/extension-form-mqtt.tpl.html | 2 +- .../extension/extensions-forms/extension-form-opc.directive.js | 2 +- .../app/extension/extensions-forms/extension-form-opc.tpl.html | 2 +- ui/src/app/extension/extensions-forms/extension-form.scss | 2 +- ui/src/app/extension/index.js | 2 +- ui/src/app/global-interceptor.service.js | 2 +- ui/src/app/help/help-links.constant.js | 2 +- ui/src/app/help/help.directive.js | 2 +- ui/src/app/help/help.scss | 2 +- ui/src/app/home/home-links.controller.js | 2 +- ui/src/app/home/home-links.routes.js | 2 +- ui/src/app/home/home-links.scss | 2 +- ui/src/app/home/home-links.tpl.html | 2 +- ui/src/app/home/index.js | 2 +- ui/src/app/ie.support.js | 2 +- ui/src/app/import-export/import-dialog-csv.controller.js | 2 +- ui/src/app/import-export/import-dialog-csv.tpl.html | 2 +- ui/src/app/import-export/import-dialog.controller.js | 2 +- ui/src/app/import-export/import-dialog.scss | 2 +- ui/src/app/import-export/import-dialog.tpl.html | 2 +- ui/src/app/import-export/import-export.service.js | 2 +- ui/src/app/import-export/index.js | 2 +- ui/src/app/import-export/table-columns-assignment.directive.js | 3 +-- ui/src/app/import-export/table-columns-assignment.tpl.html | 2 +- ui/src/app/jsonform/index.js | 2 +- ui/src/app/jsonform/jsonform.controller.js | 2 +- ui/src/app/jsonform/jsonform.routes.js | 2 +- ui/src/app/jsonform/jsonform.scss | 2 +- ui/src/app/jsonform/jsonform.tpl.html | 2 +- ui/src/app/layout/breadcrumb-icon.filter.js | 2 +- ui/src/app/layout/breadcrumb-label.filter.js | 2 +- ui/src/app/layout/breadcrumb.tpl.html | 2 +- ui/src/app/layout/home.controller.js | 2 +- ui/src/app/layout/home.routes.js | 2 +- ui/src/app/layout/home.scss | 2 +- ui/src/app/layout/home.tpl.html | 2 +- ui/src/app/layout/index.js | 2 +- ui/src/app/layout/user-menu.directive.js | 2 +- ui/src/app/layout/user-menu.scss | 2 +- ui/src/app/layout/user-menu.tpl.html | 2 +- ui/src/app/locale/translate-handler.js | 2 +- ui/src/app/login/create-password.controller.js | 2 +- ui/src/app/login/create-password.tpl.html | 2 +- ui/src/app/login/index.js | 2 +- ui/src/app/login/login.controller.js | 2 +- ui/src/app/login/login.routes.js | 2 +- ui/src/app/login/login.scss | 2 +- ui/src/app/login/login.tpl.html | 2 +- ui/src/app/login/reset-password-request.controller.js | 2 +- ui/src/app/login/reset-password-request.tpl.html | 2 +- ui/src/app/login/reset-password.controller.js | 2 +- ui/src/app/login/reset-password.tpl.html | 2 +- ui/src/app/profile/change-password.controller.js | 2 +- ui/src/app/profile/change-password.tpl.html | 2 +- ui/src/app/profile/index.js | 2 +- ui/src/app/profile/profile.controller.js | 2 +- ui/src/app/profile/profile.routes.js | 2 +- ui/src/app/profile/profile.tpl.html | 2 +- ui/src/app/rulechain/add-link.tpl.html | 2 +- ui/src/app/rulechain/add-rulechain.tpl.html | 2 +- ui/src/app/rulechain/add-rulenode.tpl.html | 2 +- ui/src/app/rulechain/index.js | 2 +- ui/src/app/rulechain/link-fieldset.tpl.html | 2 +- ui/src/app/rulechain/link.directive.js | 2 +- ui/src/app/rulechain/link.scss | 2 +- ui/src/app/rulechain/message-type-autocomplete.directive.js | 2 +- ui/src/app/rulechain/message-type-autocomplete.scss | 2 +- ui/src/app/rulechain/message-type-autocomplete.tpl.html | 2 +- ui/src/app/rulechain/rulechain-card.tpl.html | 2 +- ui/src/app/rulechain/rulechain-fieldset.tpl.html | 2 +- ui/src/app/rulechain/rulechain.controller.js | 2 +- ui/src/app/rulechain/rulechain.directive.js | 2 +- ui/src/app/rulechain/rulechain.routes.js | 2 +- ui/src/app/rulechain/rulechain.scss | 2 +- ui/src/app/rulechain/rulechain.tpl.html | 2 +- ui/src/app/rulechain/rulechains.controller.js | 2 +- ui/src/app/rulechain/rulechains.tpl.html | 2 +- ui/src/app/rulechain/rulenode-config.directive.js | 2 +- ui/src/app/rulechain/rulenode-config.tpl.html | 2 +- ui/src/app/rulechain/rulenode-defined-config.directive.js | 2 +- ui/src/app/rulechain/rulenode-fieldset.tpl.html | 2 +- ui/src/app/rulechain/rulenode.directive.js | 2 +- ui/src/app/rulechain/rulenode.scss | 2 +- ui/src/app/rulechain/rulenode.tpl.html | 2 +- ui/src/app/rulechain/script/node-script-test.controller.js | 2 +- ui/src/app/rulechain/script/node-script-test.scss | 2 +- ui/src/app/rulechain/script/node-script-test.service.js | 2 +- ui/src/app/rulechain/script/node-script-test.tpl.html | 2 +- ui/src/app/services/clipboard.service.js | 2 +- ui/src/app/services/error-toast.tpl.html | 2 +- ui/src/app/services/info-toast.tpl.html | 2 +- ui/src/app/services/item-buffer.service.js | 2 +- ui/src/app/services/menu.service.js | 2 +- ui/src/app/services/success-toast.tpl.html | 2 +- ui/src/app/services/toast.controller.js | 2 +- ui/src/app/services/toast.js | 2 +- ui/src/app/services/toast.scss | 2 +- ui/src/app/services/toast.service.js | 2 +- ui/src/app/tenant/add-tenant.tpl.html | 2 +- ui/src/app/tenant/index.js | 2 +- ui/src/app/tenant/tenant-card.tpl.html | 2 +- ui/src/app/tenant/tenant-fieldset.tpl.html | 2 +- ui/src/app/tenant/tenant.controller.js | 2 +- ui/src/app/tenant/tenant.directive.js | 2 +- ui/src/app/tenant/tenant.routes.js | 2 +- ui/src/app/tenant/tenants.tpl.html | 2 +- ui/src/app/url.handler.js | 2 +- ui/src/app/user/activation-link.controller.js | 2 +- ui/src/app/user/activation-link.dialog.tpl.html | 2 +- ui/src/app/user/add-user.controller.js | 2 +- ui/src/app/user/add-user.tpl.html | 2 +- ui/src/app/user/index.js | 2 +- ui/src/app/user/user-card.tpl.html | 2 +- ui/src/app/user/user-fieldset.scss | 2 +- ui/src/app/user/user-fieldset.tpl.html | 2 +- ui/src/app/user/user.controller.js | 2 +- ui/src/app/user/user.directive.js | 2 +- ui/src/app/user/user.routes.js | 2 +- ui/src/app/user/users.tpl.html | 2 +- ui/src/app/widget/add-widgets-bundle.tpl.html | 2 +- ui/src/app/widget/index.js | 2 +- ui/src/app/widget/lib/CanvasDigitalGauge.js | 2 +- ui/src/app/widget/lib/add-entity-panel.scss | 2 +- ui/src/app/widget/lib/add-entity-panel.tpl.html | 2 +- ui/src/app/widget/lib/alarm-status-filter-panel.scss | 2 +- ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html | 2 +- ui/src/app/widget/lib/alarms-table-widget.js | 2 +- ui/src/app/widget/lib/alarms-table-widget.scss | 2 +- ui/src/app/widget/lib/alarms-table-widget.tpl.html | 2 +- ui/src/app/widget/lib/analogue-compass.js | 2 +- ui/src/app/widget/lib/analogue-linear-gauge.js | 2 +- ui/src/app/widget/lib/analogue-radial-gauge.js | 2 +- ui/src/app/widget/lib/canvas-digital-gauge.js | 2 +- .../widget/lib/date-range-navigator/date-range-navigator.js | 2 +- .../widget/lib/date-range-navigator/date-range-navigator.scss | 2 +- .../lib/date-range-navigator/date-range-navigator.tpl.html | 2 +- ui/src/app/widget/lib/display-columns-panel.scss | 2 +- ui/src/app/widget/lib/display-columns-panel.tpl.html | 2 +- ui/src/app/widget/lib/entities-hierarchy-widget.js | 3 +-- ui/src/app/widget/lib/entities-hierarchy-widget.scss | 3 +-- ui/src/app/widget/lib/entities-hierarchy-widget.tpl.html | 2 +- ui/src/app/widget/lib/entities-table-widget.js | 2 +- ui/src/app/widget/lib/entities-table-widget.scss | 2 +- ui/src/app/widget/lib/entities-table-widget.tpl.html | 2 +- ui/src/app/widget/lib/extensions-table-widget.js | 2 +- ui/src/app/widget/lib/extensions-table-widget.scss | 2 +- ui/src/app/widget/lib/extensions-table-widget.tpl.html | 2 +- ui/src/app/widget/lib/flot-widget.js | 2 +- ui/src/app/widget/lib/google-map.js | 2 +- ui/src/app/widget/lib/image-map.js | 2 +- ui/src/app/widget/lib/map-widget.js | 2 +- ui/src/app/widget/lib/map-widget2.js | 2 +- ui/src/app/widget/lib/multiple-input-widget.js | 2 +- ui/src/app/widget/lib/multiple-input-widget.scss | 2 +- ui/src/app/widget/lib/multiple-input-widget.tpl.html | 2 +- ui/src/app/widget/lib/openstreet-map.js | 2 +- ui/src/app/widget/lib/rpc/index.js | 2 +- ui/src/app/widget/lib/rpc/knob.directive.js | 2 +- ui/src/app/widget/lib/rpc/knob.scss | 2 +- ui/src/app/widget/lib/rpc/knob.tpl.html | 2 +- ui/src/app/widget/lib/rpc/led-indicator.directive.js | 2 +- ui/src/app/widget/lib/rpc/led-indicator.scss | 2 +- ui/src/app/widget/lib/rpc/led-indicator.tpl.html | 2 +- ui/src/app/widget/lib/rpc/round-switch.directive.js | 2 +- ui/src/app/widget/lib/rpc/round-switch.scss | 2 +- ui/src/app/widget/lib/rpc/round-switch.tpl.html | 2 +- ui/src/app/widget/lib/rpc/switch.directive.js | 2 +- ui/src/app/widget/lib/rpc/switch.scss | 2 +- ui/src/app/widget/lib/rpc/switch.tpl.html | 2 +- ui/src/app/widget/lib/tencent-map.js | 2 +- ui/src/app/widget/lib/timeseries-table-widget.js | 2 +- ui/src/app/widget/lib/timeseries-table-widget.scss | 2 +- ui/src/app/widget/lib/timeseries-table-widget.tpl.html | 2 +- ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js | 2 +- ui/src/app/widget/lib/tripAnimation/trip-animation-widget.scss | 3 +-- .../widget/lib/tripAnimation/trip-animation-widget.tpl.html | 2 +- ui/src/app/widget/lib/web-camera-input-widget.js | 2 +- ui/src/app/widget/lib/web-camera-input-widget.scss | 3 +-- ui/src/app/widget/lib/web-camera-input-widget.tpl.html | 2 +- ui/src/app/widget/lib/widget-utils.js | 2 +- ui/src/app/widget/save-widget-type-as.controller.js | 2 +- ui/src/app/widget/save-widget-type-as.tpl.html | 2 +- ui/src/app/widget/select-widget-type.controller.js | 2 +- ui/src/app/widget/select-widget-type.tpl.html | 2 +- ui/src/app/widget/widget-editor.controller.js | 2 +- ui/src/app/widget/widget-editor.scss | 2 +- ui/src/app/widget/widget-editor.tpl.html | 2 +- ui/src/app/widget/widget-library.controller.js | 2 +- ui/src/app/widget/widget-library.routes.js | 2 +- ui/src/app/widget/widget-library.tpl.html | 2 +- ui/src/app/widget/widgets-bundle-card.tpl.html | 2 +- ui/src/app/widget/widgets-bundle-fieldset.tpl.html | 2 +- ui/src/app/widget/widgets-bundle.controller.js | 2 +- ui/src/app/widget/widgets-bundle.directive.js | 2 +- ui/src/app/widget/widgets-bundles.tpl.html | 2 +- ui/src/index.html | 2 +- ui/src/scss/animations.scss | 2 +- ui/src/scss/constants.scss | 2 +- ui/src/scss/fonts.scss | 2 +- ui/src/scss/main.scss | 2 +- ui/src/scss/mixins.scss | 2 +- ui/webpack.config.dev.js | 2 +- ui/webpack.config.js | 2 +- ui/webpack.config.prod.js | 2 +- 1915 files changed, 1915 insertions(+), 1934 deletions(-) diff --git a/application/build.gradle b/application/build.gradle index 1d6017c964..1ea603039a 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/application/pom.xml b/application/pom.xml index 06e2437417..881c35f87d 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ + + + + gradle-fix + Gradle fix + https://repo.gradle.org/gradle/libs-releases-local + gradle + + + diff --git a/.travis.yml b/.travis.yml index f635da5a55..516f25ae3b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ before_install: - export M2_HOME=/usr/local/maven - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false + - cp .travis.settings.xml $HOME/.m2/settings.xml jdk: - openjdk8 language: java From 25dc4e4e38ee19cbfebc2817180b25a07ede68f3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 14:42:33 +0200 Subject: [PATCH 170/261] Use https for maven central --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 486c9505f0..d19b61e7f1 100755 --- a/pom.xml +++ b/pom.xml @@ -903,7 +903,7 @@ central - http://repo1.maven.org/maven2/ + https://repo1.maven.org/maven2/ spring-snapshots @@ -924,7 +924,7 @@ typesafe Typesafe Repository - http://repo.typesafe.com/typesafe/releases/ + https://repo.typesafe.com/typesafe/releases/ sonatype From 580e751398f3fae76ca65b0779649b9d03b4b7cc Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 18:29:51 +0200 Subject: [PATCH 171/261] Use gradle-maven-plugin from org.thingsboard groupId --- .travis.settings.xml | 29 ----------------------------- .travis.yml | 1 - application/pom.xml | 2 +- msa/js-executor/pom.xml | 2 +- msa/web-ui/pom.xml | 2 +- pom.xml | 4 ++-- transport/coap/pom.xml | 2 +- transport/http/pom.xml | 2 +- transport/mqtt/pom.xml | 2 +- 9 files changed, 8 insertions(+), 38 deletions(-) delete mode 100644 .travis.settings.xml diff --git a/.travis.settings.xml b/.travis.settings.xml deleted file mode 100644 index cbcdd3ccb0..0000000000 --- a/.travis.settings.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - gradle-fix - Gradle fix - https://repo.gradle.org/gradle/libs-releases-local - gradle - - - diff --git a/.travis.yml b/.travis.yml index 516f25ae3b..f635da5a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ before_install: - export M2_HOME=/usr/local/maven - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false - - cp .travis.settings.xml $HOME/.m2/settings.xml jdk: - openjdk8 language: java diff --git a/application/pom.xml b/application/pom.xml index 881c35f87d..acadf92978 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -547,7 +547,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 03f97c3358..f85355a11e 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -231,7 +231,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 983db2322d..ad750f6706 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -255,7 +255,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/pom.xml b/pom.xml index d19b61e7f1..ef05999b58 100755 --- a/pom.xml +++ b/pom.xml @@ -171,9 +171,9 @@ ${spring-boot.version} - org.fortasoft + org.thingsboard gradle-maven-plugin - 1.0.8 + 1.0.9 org.apache.maven.plugins diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index b5fdbbb680..d17dc0ba5b 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/transport/http/pom.xml b/transport/http/pom.xml index f464fd6679..72572d943f 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 69062a5e81..cb1844eea5 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -260,7 +260,7 @@ - org.fortasoft + org.thingsboard gradle-maven-plugin From 7d0661a8262f27fff99b4384598878aa09c7357c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 16 Jan 2020 18:42:40 +0200 Subject: [PATCH 172/261] Update package-lock verions --- msa/js-executor/package-lock.json | 2 +- msa/web-ui/package-lock.json | 2 +- ui/package-lock.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/package-lock.json b/msa/js-executor/package-lock.json index 96c8341882..77d1d60b39 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.2", + "version": "2.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/msa/web-ui/package-lock.json b/msa/web-ui/package-lock.json index 3ceb92e809..34e9cb08a2 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.2", + "version": "2.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/ui/package-lock.json b/ui/package-lock.json index c8c8df6fdc..74db613d09 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "2.4.2", + "version": "2.4.3", "lockfileVersion": 1, "requires": true, "dependencies": { From e583b0a1d3c07bed59ac80fd0e8c2ce3186cef5f Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Mon, 20 Jan 2020 15:02:00 +0200 Subject: [PATCH 173/261] Feature/rest client (#2347) * refactored URLs * refactored * refactored * refactored * refactored * refactored rest client * changed executorService from RestClient * refactored rest client and JsonConverter --- .../thingsboard/client/tools/RestClient.java | 309 ++++++++++-------- .../client/tools/utils/RestJsonConverter.java | 87 +++++ 2 files changed, 258 insertions(+), 138 deletions(-) create mode 100644 tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java 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 0bb59c4222..9b64f2292a 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -31,7 +31,7 @@ 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; +import org.thingsboard.client.tools.utils.RestJsonConverter; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; @@ -57,6 +57,8 @@ 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.kv.AttributeKvEntry; +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.data.page.TimePageData; @@ -74,6 +76,7 @@ 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; +import java.io.Closeable; import java.io.IOException; import java.net.URI; import java.util.Collections; @@ -81,19 +84,23 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import static org.springframework.util.StringUtils.isEmpty; /** * @author Andrew Shvayka */ -public class RestClient implements ClientHttpRequestInterceptor { +public class RestClient implements ClientHttpRequestInterceptor, Closeable { private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; protected final RestTemplate restTemplate; protected final String baseURL; private String token; private String refreshToken; private final ObjectMapper objectMapper = new ObjectMapper(); + private ExecutorService service = Executors.newWorkStealingPool(10); protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; @@ -256,6 +263,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody(); } + public Asset createAsset(Asset asset) { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } @@ -418,17 +426,17 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void ackAlarm(String alarmId) { - restTemplate.postForObject(baseURL + "/api/alarm/{alarmId}/ack", new Object(), Object.class, alarmId); + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/ack", null, alarmId); } public void clearAlarm(String alarmId) { - restTemplate.postForObject(baseURL + "/api/alarm/{alarmId}/clear", new Object(), Object.class, alarmId); + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId); } - public TimePageData getAlarms(String entityType, String entityId, String searchStatus, String status, TimePageLink pageLink, Boolean fetchOriginator) { + public TimePageData getAlarms(EntityId entityId, String searchStatus, String status, TimePageLink pageLink, Boolean fetchOriginator) { Map params = new HashMap<>(); - params.put("entityType", entityType); - params.put("entityId", entityId); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); params.put("searchStatus", searchStatus); params.put("status", status); params.put("fetchOriginator", String.valueOf(fetchOriginator)); @@ -471,10 +479,10 @@ public class RestClient implements ClientHttpRequestInterceptor { return urlParams; } - public Optional getHighestAlarmSeverity(String entityType, String entityId, String searchStatus, String status) { + public Optional getHighestAlarmSeverity(EntityId entityId, String searchStatus, String status) { Map params = new HashMap<>(); - params.put("entityType", entityType); - params.put("entityId", entityId); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); params.put("searchStatus", searchStatus); params.put("status", status); try { @@ -597,14 +605,14 @@ public class RestClient implements ClientHttpRequestInterceptor { return assets.getBody(); } - public List getAssetsByIds(String[] assetIds) { + public List getAssetsByIds(List assetIds) { return restTemplate.exchange( baseURL + "/api/assets?assetIds={assetIds}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - String.join(",", assetIds)).getBody(); + listToString(assetIds)).getBody(); } public List findByQuery(AssetSearchQuery query) { @@ -657,10 +665,10 @@ public class RestClient implements ClientHttpRequestInterceptor { return auditLog.getBody(); } - public TimePageData getAuditLogsByEntityId(String entityType, String entityId, String actionTypes, TimePageLink pageLink) { + public TimePageData getAuditLogsByEntityId(EntityId entityId, String actionTypes, TimePageLink pageLink) { Map params = new HashMap<>(); - params.put("entityType", entityType); - params.put("entityId", entityId); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); params.put("actionTypes", actionTypes); addPageLinkToParam(params, pageLink); @@ -700,14 +708,14 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void logout() { - restTemplate.exchange(URI.create(baseURL + "/api/auth/logout"), HttpMethod.POST, HttpEntity.EMPTY, Object.class); + restTemplate.postForLocation(baseURL + "/api/auth/logout", null); } 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); + restTemplate.postForLocation(baseURL + "/api/auth/changePassword", changePasswordRequest); } public Optional getUserPasswordPolicy() { @@ -731,7 +739,7 @@ public class RestClient implements ClientHttpRequestInterceptor { 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); + restTemplate.postForLocation(baseURL + "/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest); } public Optional activateUser(String userId, String password) { @@ -772,14 +780,14 @@ public class RestClient implements ClientHttpRequestInterceptor { componentType).getBody(); } - public List getComponentDescriptorsByTypes(String[] componentTypes) { + public List getComponentDescriptorsByTypes(List componentTypes) { return restTemplate.exchange( baseURL + "/api/components?componentTypes={componentTypes}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - String.join(",", componentTypes)).getBody(); + listToString(componentTypes)).getBody(); } public Optional getCustomerById(String customerId) { @@ -915,7 +923,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Optional updateDashboardCustomers(String dashboardId, String[] customerIds) { + public Optional updateDashboardCustomers(String dashboardId, List customerIds) { try { ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); @@ -928,7 +936,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Optional addDashboardCustomers(String dashboardId, String[] customerIds) { + public Optional addDashboardCustomers(String dashboardId, List customerIds) { try { ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/add", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); @@ -941,7 +949,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public Optional removeDashboardCustomers(String dashboardId, String[] customerIds) { + public Optional removeDashboardCustomers(String dashboardId, List customerIds) { try { ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/remove", customerIds, Dashboard.class, dashboardId); return Optional.ofNullable(dashboard.getBody()); @@ -1135,12 +1143,12 @@ public class RestClient implements ClientHttpRequestInterceptor { .getBody(); } - public List getDevicesByIds(String[] deviceIds) { + public List getDevicesByIds(List deviceIds) { return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - String.join(",", deviceIds)).getBody(); + listToString(deviceIds)).getBody(); } public List findByQuery(DeviceSearchQuery query) { @@ -1161,28 +1169,22 @@ public class RestClient implements ClientHttpRequestInterceptor { }).getBody(); } - public DeferredResult claimDevice(String deviceName, ClaimRequest claimRequest) { + public JsonNode claimDevice(String deviceName, ClaimRequest claimRequest) { return restTemplate.exchange( baseURL + "/api/customer/device/{deviceName}/claim", HttpMethod.POST, new HttpEntity<>(claimRequest), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference() { }, deviceName).getBody(); } - public DeferredResult reClaimDevice(String deviceName) { - return restTemplate.exchange( - baseURL + "/api/customer/device/{deviceName}/claim", - HttpMethod.DELETE, - HttpEntity.EMPTY, - new ParameterizedTypeReference>() { - }, - deviceName).getBody(); + public void reClaimDevice(String deviceName) { + restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); } public void saveRelation(EntityRelation relation) { - restTemplate.postForEntity(baseURL + "/api/relation", relation, Object.class); + restTemplate.postForLocation(baseURL + "/api/relation", null); } public void deleteRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { @@ -1196,8 +1198,8 @@ public class RestClient implements ClientHttpRequestInterceptor { 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 + "/api/relations?entityId={entityId}&entityType={entityType}", entityId, entityType); + public void deleteRelations(EntityId entityId) { + restTemplate.delete(baseURL + "/api/relations?entityId={entityId}&entityType={entityType}", entityId.getId().toString(), entityId.getEntityType().name()); } public Optional getRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { @@ -1448,10 +1450,10 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public TimePageData getEvents(String entityType, String entityId, String eventType, String tenantId, TimePageLink pageLink) { + public TimePageData getEvents(EntityId entityId, String eventType, String tenantId, TimePageLink pageLink) { Map params = new HashMap<>(); - params.put("entityType", entityType); - params.put("entityId", entityId); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); params.put("eventType", eventType); params.put("tenantId", tenantId); addPageLinkToParam(params, pageLink); @@ -1465,10 +1467,10 @@ public class RestClient implements ClientHttpRequestInterceptor { params).getBody(); } - public TimePageData getEvents(String entityType, String entityId, String tenantId, TimePageLink pageLink) { + public TimePageData getEvents(EntityId entityId, String tenantId, TimePageLink pageLink) { Map params = new HashMap<>(); - params.put("entityType", entityType); - params.put("entityId", entityId); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); params.put("tenantId", tenantId); addPageLinkToParam(params, pageLink); @@ -1481,22 +1483,16 @@ public class RestClient implements ClientHttpRequestInterceptor { params).getBody(); } - public DeferredResult handleOneWayDeviceRPCRequest(String deviceId, String requestBody) { - return restTemplate.exchange( - baseURL + "/api/plugins/rpc/oneway/{deviceId}", - HttpMethod.POST, - new HttpEntity<>(requestBody), - new ParameterizedTypeReference>() { - }, - deviceId).getBody(); + public void handleOneWayDeviceRPCRequest(String deviceId, JsonNode requestBody) { + restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId); } - public DeferredResult handleTwoWayDeviceRPCRequest(String deviceId, String requestBody) { + public JsonNode handleTwoWayDeviceRPCRequest(String deviceId, JsonNode requestBody) { return restTemplate.exchange( baseURL + "/api/plugins/rpc/twoway/{deviceId}", HttpMethod.POST, new HttpEntity<>(requestBody), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference() { }, deviceId).getBody(); } @@ -1590,206 +1586,233 @@ public class RestClient implements ClientHttpRequestInterceptor { } } - public DeferredResult getAttributeKeys(String entityType, String entityId) { + public List getAttributeKeys(EntityId entityId) { return restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId).getBody(); + entityId.getEntityType().name(), + entityId.getId().toString()).getBody(); } - public DeferredResult getAttributeKeysByScope(String entityType, String entityId, String scope) { + public List getAttributeKeysByScope(EntityId entityId, String scope) { return restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes/{scope}", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope).getBody(); } - public DeferredResult getAttributesResponseEntity(String entityType, String entityId, String keys) { - return restTemplate.exchange( + public List getAttributeKvEntries(EntityId entityId, List keys) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, - keys).getBody(); + entityId.getEntityType().name(), + entityId.getId(), + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult getAttributesByScope(String entityType, String entityId, String scope, String keys) { - return restTemplate.exchange( + public Future> getAttributeKvEntriesAsync(EntityId entityId, List keys) { + return service.submit(() -> getAttributeKvEntries(entityId, keys)); + } + + public List getAttributesByScope(EntityId entityId, String scope, List keys) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope, - keys).getBody(); + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult getTimeseriesKeys(String entityType, String entityId) { + public List getTimeseriesKeys(EntityId entityId) { return restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/timeseries", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId).getBody(); + entityId.getEntityType().name(), + entityId.getId().toString()).getBody(); } - public DeferredResult getLatestTimeseries(String entityType, String entityId, String keys) { - return restTemplate.exchange( + public List getLatestTimeseries(EntityId entityId, List keys) { + Map> timeseries = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}", HttpMethod.GET, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>>() { }, - entityType, - entityId, - keys).getBody(); + entityId.getEntityType().name(), + entityId.getId().toString(), + listToString(keys)).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); } - public DeferredResult getTimeseries(String entityType, String entityId, String keys, Long startTs, Long endTs, Long interval, Integer limit, String agg) { + public List getTimeseries(EntityId entityId, List 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("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("keys", listToString(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( + Map> timeseries = restTemplate.exchange( 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>() { + new ParameterizedTypeReference>>() { }, params).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); } - public DeferredResult saveDeviceAttributes(String deviceId, String scope, JsonNode request) { - return restTemplate.exchange( + public List saveDeviceAttributes(String deviceId, String scope, JsonNode request) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{deviceId}/{scope}", HttpMethod.POST, new HttpEntity<>(request), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, deviceId, scope).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult saveEntityAttributesV1(String entityType, String entityId, String scope, JsonNode request) { - return restTemplate.exchange( + public List saveEntityAttributesV1(EntityId entityId, String scope, JsonNode request) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}", HttpMethod.POST, new HttpEntity<>(request), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult saveEntityAttributesV2(String entityType, String entityId, String scope, JsonNode request) { - return restTemplate.exchange( + public List saveEntityAttributesV2(EntityId entityId, String scope, JsonNode request) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", HttpMethod.POST, new HttpEntity<>(request), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult saveEntityTelemetry(String entityType, String entityId, String scope, String requestBody) { - return restTemplate.exchange( + public List saveEntityTelemetry(EntityId entityId, String scope, String requestBody) { + Map> timeseries = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}", HttpMethod.POST, new HttpEntity<>(requestBody), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); } - public DeferredResult saveEntityTelemetryWithTTL(String entityType, String entityId, String scope, Long ttl, String requestBody) { - return restTemplate.exchange( + public List saveEntityTelemetryWithTTL(EntityId entityId, String scope, Long ttl, String requestBody) { + Map> timeseries = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}/{ttl}", HttpMethod.POST, new HttpEntity<>(requestBody), - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope, ttl).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); } - public DeferredResult deleteEntityTimeseries(String entityType, - String entityId, - String keys, - boolean deleteAllDataForKeys, - Long startTs, - Long endTs, - boolean rewriteLatestIfDeleted) { + public List deleteEntityTimeseries(EntityId entityId, + List 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("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("keys", listToString(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( + Map> timeseries = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", HttpMethod.DELETE, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>>() { }, params).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); } - public DeferredResult deleteEntityAttributes(String deviceId, String scope, String keys) { - return restTemplate.exchange( + public List deleteEntityAttributes(String deviceId, String scope, List keys) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{deviceId}/{scope}?keys={keys}", HttpMethod.DELETE, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, deviceId, scope, - keys).getBody(); + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); } - public DeferredResult deleteEntityAttributes(String entityType, String entityId, String scope, String keys) { - return restTemplate.exchange( + public List deleteEntityAttributes(EntityId entityId, String scope, List keys) { + List attributes = restTemplate.exchange( baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}?keys={keys}", HttpMethod.DELETE, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, - entityType, - entityId, + entityId.getEntityType().name(), + entityId.getId().toString(), scope, - keys).getBody(); + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); } public Optional getTenantById(String tenantId) { @@ -1860,7 +1883,7 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void sendActivationEmail(String email) { - restTemplate.postForEntity(baseURL + "/api/user/sendActivationMail?email={email}", null, Object.class, email); + restTemplate.postForLocation(baseURL + "/api/user/sendActivationMail?email={email}", null, email); } public String getActivationLink(String userId) { @@ -1900,10 +1923,9 @@ public class RestClient implements ClientHttpRequestInterceptor { } public void setUserCredentialsEnabled(String userId, boolean userCredentialsEnabled) { - restTemplate.postForEntity( + restTemplate.postForLocation( baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", null, - Object.class, userId, userCredentialsEnabled); } @@ -2030,4 +2052,15 @@ public class RestClient implements ClientHttpRequestInterceptor { params.put("textOffset", pageLink.getTextOffset()); } } + + private String listToString(List list) { + return String.join(",", list); + } + + @Override + public void close() { + if (service != null) { + service.shutdown(); + } + } } diff --git a/tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java b/tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java new file mode 100644 index 0000000000..5e70e78659 --- /dev/null +++ b/tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2020 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.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.util.CollectionUtils; +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.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 java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RestJsonConverter { + private static final String KEY = "key"; + private static final String VALUE = "value"; + private static final String LAST_UPDATE_TS = "lastUpdateTs"; + private static final String TS = "ts"; + + private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; + + public static List toAttributes(List attributes) { + if (!CollectionUtils.isEmpty(attributes)) { + return attributes.stream().map(attr -> { + KvEntry entry = parseValue(attr.get(KEY).asText(), attr.get(VALUE)); + return new BaseAttributeKvEntry(entry, attr.get(LAST_UPDATE_TS).asLong()); + } + ).collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + public static List toTimeseries(Map> timeseries) { + if (!CollectionUtils.isEmpty(timeseries)) { + List result = new ArrayList<>(); + timeseries.forEach((key, values) -> + result.addAll(values.stream().map(ts -> { + KvEntry entry = parseValue(key, ts.get(VALUE)); + return new BasicTsKvEntry(ts.get(TS).asLong(), entry); + } + ).collect(Collectors.toList())) + ); + return result; + } else { + return Collections.emptyList(); + } + } + + private static KvEntry parseValue(String key, JsonNode value) { + if (!value.isObject()) { + if (value.isBoolean()) { + return new BooleanDataEntry(key, value.asBoolean()); + } else if (value.isDouble()) { + return new DoubleDataEntry(key, value.asDouble()); + } else if (value.isLong()) { + return new LongDataEntry(key, value.asLong()); + } else { + return new StringDataEntry(key, value.asText()); + } + } else { + throw new RuntimeException(CAN_T_PARSE_VALUE + value); + } + } +} From 0dd313dd61e1d1dbeb89e3f326cb81b944ecabe4 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 20 Jan 2020 15:24:15 +0200 Subject: [PATCH 174/261] Update stylelint to 13.0.0 (#2348) * Update stylelint to 13.0.0 * Update style --- ui/package.json | 12 +++--- ui/src/app/entity/entity-list.scss | 1 + ui/src/app/entity/entity-select.scss | 1 + ui/src/app/entity/entity-subtype-list.scss | 1 + ui/src/app/entity/entity-type-list.scss | 1 + ui/src/app/entity/entity-type-select.scss | 1 + .../app/widget/lib/alarms-table-widget.scss | 42 ++++--------------- .../app/widget/lib/entities-table-widget.scss | 42 ++++--------------- 8 files changed, 25 insertions(+), 76 deletions(-) diff --git a/ui/package.json b/ui/package.json index f9f6083b31..ccf18eab93 100644 --- a/ui/package.json +++ b/ui/package.json @@ -135,12 +135,12 @@ "react-hot-loader": "^4.12.8", "sass-loader": "^7.1.0", "style-loader": "^0.23.1", - "stylelint": "^8.4.0", - "stylelint-config-recommended-scss": "^3.3.0", - "stylelint-config-standard": "^18.3.0", - "stylelint-order": "^3.0.1", - "stylelint-scss": "^3.9.2", - "stylelint-webpack-plugin": "^0.10.5", + "stylelint": "13.0.0", + "stylelint-config-recommended-scss": "4.1.0", + "stylelint-config-standard": "19.0.0", + "stylelint-order": "4.0.0", + "stylelint-scss": "3.13.0", + "stylelint-webpack-plugin": "^1.2.1", "uglifyjs-webpack-plugin": "^2.1.3", "url-loader": "^2.1.0", "webpack": "^4.37.0", diff --git a/ui/src/app/entity/entity-list.scss b/ui/src/app/entity/entity-list.scss index 94bcc56613..0b5826e5d4 100644 --- a/ui/src/app/entity/entity-list.scss +++ b/ui/src/app/entity/entity-list.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* .tb-entity-list { #entity_list_chips { diff --git a/ui/src/app/entity/entity-select.scss b/ui/src/app/entity/entity-select.scss index 95f43885e4..a622807890 100644 --- a/ui/src/app/entity/entity-select.scss +++ b/ui/src/app/entity/entity-select.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* .tb-entity-select { } diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss index 1705f880a0..59f14a4f54 100644 --- a/ui/src/app/entity/entity-subtype-list.scss +++ b/ui/src/app/entity/entity-subtype-list.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* .tb-entity-subtype-list { #entity_subtype_list_chips { diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss index 2147227fb0..069bb985f8 100644 --- a/ui/src/app/entity/entity-type-list.scss +++ b/ui/src/app/entity/entity-type-list.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* .tb-entity-type-list { #entity_type_list_chips { diff --git a/ui/src/app/entity/entity-type-select.scss b/ui/src/app/entity/entity-type-select.scss index c860aecf21..c94827702d 100644 --- a/ui/src/app/entity/entity-type-select.scss +++ b/ui/src/app/entity/entity-type-select.scss @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + /* md-select.tb-entity-type-select { } diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss index b4f57b5371..427323890e 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.scss +++ b/ui/src/app/widget/lib/alarms-table-widget.scss @@ -43,43 +43,15 @@ &.tb-data-table { table.md-table, table.md-table.md-row-select { - th.md-column { - &.tb-action-cell { - .md-button { - /* stylelint-disable-next-line selector-max-class */ - &.md-icon-button { - width: 36px; - height: 36px; - padding: 6px; - margin: 0; - /* stylelint-disable-next-line selector-max-class */ - md-icon { - width: 24px; - height: 24px; - font-size: 24px !important; - line-height: 24px !important; - } - } - } - } - } - tbody { - tr { - td { - &.tb-action-cell { - width: 36px; - min-width: 36px; - max-width: 36px; + .tb-action-cell { + width: 36px; + min-width: 36px; + max-width: 36px; - .md-button[disabled] { - &.md-icon-button { - /* stylelint-disable-next-line selector-max-class */ - md-icon { - color: rgba(0, 0, 0, .38); - } - } - } + .md-button[disabled] { + md-icon { + color: rgba(0, 0, 0, .38); } } } diff --git a/ui/src/app/widget/lib/entities-table-widget.scss b/ui/src/app/widget/lib/entities-table-widget.scss index 2b4fc0d322..cf45c30ca1 100644 --- a/ui/src/app/widget/lib/entities-table-widget.scss +++ b/ui/src/app/widget/lib/entities-table-widget.scss @@ -43,43 +43,15 @@ &.tb-data-table { table.md-table, table.md-table.md-row-select { - th.md-column { - &.tb-action-cell { - .md-button { - /* stylelint-disable-next-line selector-max-class */ - &.md-icon-button { - width: 36px; - height: 36px; - padding: 6px; - margin: 0; - /* stylelint-disable-next-line selector-max-class */ - md-icon { - width: 24px; - height: 24px; - font-size: 24px !important; - line-height: 24px !important; - } - } - } - } - } - tbody { - tr { - td { - &.tb-action-cell { - width: 36px; - min-width: 36px; - max-width: 36px; + .tb-action-cell { + width: 36px; + min-width: 36px; + max-width: 36px; - .md-button[disabled] { - &.md-icon-button { - /* stylelint-disable-next-line selector-max-class */ - md-icon { - color: rgba(0, 0, 0, .38); - } - } - } + .md-button[disabled] { + md-icon { + color: rgba(0, 0, 0, .38); } } } From cc0e5417887266847e4e5ac2cf9b41a8023bdba5 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 29 Jan 2020 16:58:52 +0200 Subject: [PATCH 175/261] fixed tell-failure for attributes fetch node (#2365) * fixed tell-failure for attributes fetch node --- .../server/common/data/DataConstants.java | 1 + .../metadata/TbAbstractGetAttributesNode.java | 81 +++++++++++++------ 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 6e606e2ad5..afb0dbeba6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -27,6 +27,7 @@ public class DataConstants { public static final String CLIENT_SCOPE = "CLIENT_SCOPE"; public static final String SERVER_SCOPE = "SERVER_SCOPE"; public static final String SHARED_SCOPE = "SHARED_SCOPE"; + public static final String LATEST_TS = "LATEST_TS"; public static final String[] allScopes() { return new String[]{CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE}; 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 9cbd433173..4db628a7f4 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 @@ -29,16 +29,20 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; +import java.util.ArrayList; import java.util.List; - +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import static org.thingsboard.common.util.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; +import static org.thingsboard.server.common.data.DataConstants.LATEST_TS; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; @@ -82,40 +86,43 @@ public abstract class TbAbstractGetAttributesNode> failuresMap = new ConcurrentHashMap<>(); ListenableFuture> allFutures = Futures.allAsList( - putLatestTelemetry(ctx, entityId, msg, config.getLatestTsKeyNames()), - putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"), - putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"), - putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_") + putLatestTelemetry(ctx, entityId, msg, LATEST_TS, config.getLatestTsKeyNames(), failuresMap), + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, config.getClientAttributeNames(), failuresMap, "cs_"), + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, config.getSharedAttributeNames(), failuresMap, "shared_"), + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, config.getServerAttributeNames(), failuresMap, "ss_") ); - withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + withCallback(allFutures, i -> { + if (!failuresMap.isEmpty()) { + throw reportFailures(failuresMap); + } + ctx.tellNext(msg, SUCCESS); + }, t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } - private ListenableFuture putAttrAsync(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, String prefix) { + private ListenableFuture putAttrAsync(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, ConcurrentHashMap> failuresMap, String prefix) { if (CollectionUtils.isEmpty(keys)) { return Futures.immediateFuture(null); } - ListenableFuture> latest = ctx.getAttributesService().find(ctx.getTenantId(), entityId, scope, keys); - return Futures.transform(latest, l -> { - l.forEach(r -> { + ListenableFuture> attributeKvEntryListFuture = ctx.getAttributesService().find(ctx.getTenantId(), entityId, scope, keys); + return Futures.transform(attributeKvEntryListFuture, attributeKvEntryList -> { + if (!CollectionUtils.isEmpty(attributeKvEntryList)) { + List existingAttributesKvEntry = attributeKvEntryList.stream().filter(attributeKvEntry -> keys.contains(attributeKvEntry.getKey())).collect(Collectors.toList()); + existingAttributesKvEntry.forEach(kvEntry -> msg.getMetaData().putValue(prefix + kvEntry.getKey(), kvEntry.getValueAsString())); + if (existingAttributesKvEntry.size() != keys.size() && BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { + getNotExistingKeys(existingAttributesKvEntry, keys).forEach(key -> computeFailuresMap(scope, failuresMap, key)); + } + } else { if (BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { - if (r.getValue() != null) { - msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString()); - } else { - throw new RuntimeException("[" + scope + "][" + r.getKey() + "] attribute value is not present in the DB!"); - } - } else { - if (r.getValue() != null) { - msg.getMetaData().putValue(prefix + r.getKey(), r.getValueAsString()); - } + keys.forEach(key -> computeFailuresMap(scope, failuresMap, key)); } - - }); + } return null; }); } - private ListenableFuture putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, List keys) { + private ListenableFuture putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, ConcurrentHashMap> failuresMap) { if (CollectionUtils.isEmpty(keys)) { return Futures.immediateFuture(null); } @@ -125,7 +132,7 @@ public abstract class TbAbstractGetAttributesNode getNotExistingKeys(List existingAttributesKvEntry, List allKeys) { + List existingKeys = existingAttributesKvEntry.stream().map(KvEntry::getKey).collect(Collectors.toList()); + return allKeys.stream().filter(key -> !existingKeys.contains(key)).collect(Collectors.toList()); + } + + private void computeFailuresMap(String scope, ConcurrentHashMap> failuresMap, String key) { + List failures = failuresMap.computeIfAbsent(scope, k -> new ArrayList<>()); + failures.add(key); + } + + private RuntimeException reportFailures(ConcurrentHashMap> failuresMap) { + StringBuilder errorMessage = new StringBuilder("The following attribute/telemetry keys is not present in the DB: ").append("\n"); + if (failuresMap.containsKey(CLIENT_SCOPE)) { + errorMessage.append("\t").append("[" + CLIENT_SCOPE + "]:").append(failuresMap.get(CLIENT_SCOPE).toString()).append("\n"); + } + if (failuresMap.containsKey(SERVER_SCOPE)) { + errorMessage.append("\t").append("[" + SERVER_SCOPE + "]:").append(failuresMap.get(SERVER_SCOPE).toString()).append("\n"); + } + if (failuresMap.containsKey(SHARED_SCOPE)) { + errorMessage.append("\t").append("[" + SHARED_SCOPE + "]:").append(failuresMap.get(SHARED_SCOPE).toString()).append("\n"); + } + if (failuresMap.containsKey(LATEST_TS)) { + errorMessage.append("\t").append("[" + LATEST_TS + "]:").append(failuresMap.get(LATEST_TS).toString()).append("\n"); + } + failuresMap.clear(); + return new RuntimeException(errorMessage.toString()); + } } From 84cb471e0d7e2e49807a232b87c949d44e377fb2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 29 Jan 2020 12:53:46 +0200 Subject: [PATCH 176/261] Sql timeseries improvements (#2033) * init commit * cleaned code and add test-properties * cleaned code * psql-update * timescale-update * code-refactoring * fix typo * renamed dao * revert indents * refactored code * fix typo * init-partitioning * code updated * cleaned code * fixed license * fix typo * fixed code after review * add annotation to repository * update psql version for docker * postgres-10 * postgres-10 * update docker compose config * fixed partition saving * change key_id to serial column definition * upgrade psql added * add separate upgrade service * added upgrade script * change image on k8s * change logs * resolve conflict after merge with master * revert datasource url in yml * fix typo * license header fix * remove old methods for the timeseries inserts * clean up code * fix saveOrUpdate for PostgreSQL * refactoring & revert Timescale to use latest table * added PsqlTsAnyDao * duplicated code method removed * remove unused invert dictionary map * change the upgrade directory from 2.4.1 to 2.4.3 * refactor JpaPsqlTimeseriesDao --- .../upgrade/2.4.3/schema_update_psql_ts.sql | 179 +++++++ .../install/ThingsboardInstallService.java | 35 +- .../CassandraDatabaseUpgradeService.java | 2 +- .../DatabaseEntitiesUpgradeService.java | 22 + ...ice.java => DatabaseTsUpgradeService.java} | 4 +- ....java => HsqlTsDatabaseSchemaService.java} | 8 +- .../install/PsqlTsDatabaseSchemaService.java | 32 ++ .../install/PsqlTsDatabaseUpgradeService.java | 145 ++++++ .../install/SqlDatabaseUpgradeService.java | 8 +- .../src/main/resources/thingsboard.yml | 4 +- .../controller/ControllerSqlTestSuite.java | 2 +- .../server/mqtt/MqttSqlTestSuite.java | 2 +- .../server/rules/RuleEngineSqlTestSuite.java | 2 +- .../server/system/SystemSqlTestSuite.java | 2 +- .../server/dao/util/PsqlTsAnyDao.java | 23 + ...lTsDaoConfig.java => HsqlTsDaoConfig.java} | 10 +- .../server/dao/PsqlTsDaoConfig.java | 37 ++ .../server/dao/TimescaleDaoConfig.java | 6 +- .../dao/audit/CassandraAuditLogDao.java | 6 +- .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/AbstractTsKvEntity.java | 30 +- .../sqlts/dictionary/TsKvDictionary.java | 45 ++ .../TsKvDictionaryCompositeKey.java | 34 ++ .../sqlts/{ts => hsql}/TsKvCompositeKey.java | 3 +- .../model/sqlts/{ts => hsql}/TsKvEntity.java | 32 +- .../TsKvLatestCompositeKey.java | 2 +- .../{ts => latest}/TsKvLatestEntity.java | 38 +- .../model/sqlts/psql/TsKvCompositeKey.java | 37 ++ .../dao/model/sqlts/psql/TsKvEntity.java | 135 ++++++ .../timescale/TimescaleTsKvCompositeKey.java | 7 +- .../sqlts/timescale/TimescaleTsKvEntity.java | 46 +- ...paAbstractDaoListeningExecutorService.java | 5 - .../dao/sqlts/AbstractInsertRepository.java | 51 -- .../sqlts/AbstractLatestInsertRepository.java | 58 --- .../sqlts/AbstractSimpleSqlTimeseriesDao.java | 156 +++++++ .../dao/sqlts/AbstractSqlTimeseriesDao.java | 163 ++++++- .../AbstractTimeseriesInsertRepository.java | 58 --- .../server/dao/sqlts/EntityContainer.java | 29 ++ .../dao/sqlts/InsertLatestRepository.java | 26 ++ .../server/dao/sqlts/InsertTsRepository.java | 26 ++ .../dictionary/TsKvDictionaryRepository.java | 30 ++ .../hsql/HsqlTimeseriesInsertRepository.java | 89 ++++ .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 206 +++++++++ .../TsKvHsqlRepository.java} | 10 +- .../latest/HsqlLatestInsertRepository.java | 85 ++++ .../PsqlLatestInsertRepository.java | 72 +-- .../{ts => latest}/TsKvLatestRepository.java | 6 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 312 +++++++++++++ .../psql/PsqlPartitioningRepository.java | 41 ++ .../psql/PsqlTimeseriesInsertRepository.java | 101 ++++ .../dao/sqlts/psql/TsKvPsqlRepository.java | 132 ++++++ .../timescale/AggregationRepository.java | 15 +- .../timescale/TimescaleInsertRepository.java | 78 +--- .../timescale/TimescaleTimeseriesDao.java | 231 +++++----- .../timescale/TsKvTimescaleRepository.java | 23 +- .../sqlts/ts/HsqlLatestInsertRepository.java | 140 ------ .../ts/HsqlTimeseriesInsertRepository.java | 141 ------ .../server/dao/sqlts/ts/JpaTimeseriesDao.java | 436 ------------------ .../ts/PsqlTimeseriesInsertRepository.java | 143 ------ .../CassandraBaseTimeseriesDao.java | 4 +- ...ionDate.java => NoSqlTsPartitionDate.java} | 10 +- .../server/dao/timeseries/PsqlPartition.java | 43 ++ .../dao/timeseries/SqlTsPartitionDate.java | 93 ++++ .../main/resources/sql/schema-timescale.sql | 22 +- .../sql/{schema-ts.sql => schema-ts-hsql.sql} | 0 dao/src/main/resources/sql/schema-ts-psql.sql | 43 ++ .../server/dao/AbstractJpaDaoTest.java | 2 +- .../server/dao/JpaDaoTestSuite.java | 16 +- .../server/dao/SqlDaoServiceTestSuite.java | 16 +- dao/src/test/resources/sql-test.properties | 24 +- .../sql/timescale/drop-all-tables.sql | 1 + docker/docker-compose.postgres.yml | 2 +- k8s/postgres.yml | 2 +- msa/tb/docker-postgres/start-db.sh | 4 +- msa/tb/docker-postgres/stop-db.sh | 2 +- 75 files changed, 2669 insertions(+), 1417 deletions(-) create mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql create mode 100644 application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java rename application/src/main/java/org/thingsboard/server/service/install/{DatabaseUpgradeService.java => DatabaseTsUpgradeService.java} (94%) rename application/src/main/java/org/thingsboard/server/service/install/{SqlTsDatabaseSchemaService.java => HsqlTsDatabaseSchemaService.java} (80%) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java rename dao/src/main/java/org/thingsboard/server/dao/{SqlTsDaoConfig.java => HsqlTsDaoConfig.java} (74%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{ts => hsql}/TsKvCompositeKey.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{ts => hsql}/TsKvEntity.java (78%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{ts => latest}/TsKvLatestCompositeKey.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{ts => latest}/TsKvLatestEntity.java (60%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvCompositeKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java delete 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/AbstractSimpleSqlTimeseriesDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/EntityContainer.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ts/TsKvRepository.java => hsql/TsKvHsqlRepository.java} (96%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ts => latest}/PsqlLatestInsertRepository.java (64%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ts => latest}/TsKvLatestRepository.java (83%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java rename dao/src/main/java/org/thingsboard/server/dao/timeseries/{TsPartitionDate.java => NoSqlTsPartitionDate.java} (87%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/timeseries/PsqlPartition.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlTsPartitionDate.java rename dao/src/main/resources/sql/{schema-ts.sql => schema-ts-hsql.sql} (100%) create mode 100644 dao/src/main/resources/sql/schema-ts-psql.sql diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql new file mode 100644 index 0000000000..07e0b51511 --- /dev/null +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql @@ -0,0 +1,179 @@ +-- +-- Copyright © 2016-2020 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. +-- + +-- load function check_version() + +CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ +DECLARE + current_version integer; + valid_version boolean; +BEGIN + RAISE NOTICE 'Check the current installed PostgreSQL version...'; + SELECT current_setting('server_version_num') INTO current_version; + IF current_version < 100000 THEN + valid_version := FALSE; + ELSE + valid_version := TRUE; + END IF; + IF valid_version = FALSE THEN + RAISE NOTICE 'Postgres version should be at least more than 10!'; + ELSE + RAISE NOTICE 'PostgreSQL version is valid!'; + RAISE NOTICE 'Schema update started...'; + END IF; + RETURN valid_version; +END; +$$ LANGUAGE 'plpgsql'; + +-- load function create_partition_table() + +CREATE OR REPLACE FUNCTION create_partition_table() RETURNS VOID AS $$ + +BEGIN + ALTER TABLE ts_kv + RENAME TO ts_kv_old; + CREATE TABLE IF NOT EXISTS ts_kv + ( + LIKE ts_kv_old + ) + PARTITION BY RANGE (ts); + ALTER TABLE ts_kv + DROP COLUMN entity_type; + ALTER TABLE ts_kv + ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; + ALTER TABLE ts_kv + ALTER COLUMN key TYPE integer USING key::integer; +END; +$$ LANGUAGE 'plpgsql'; + + +-- load function create_partitions() + +CREATE OR REPLACE FUNCTION create_partitions() RETURNS VOID AS +$$ +DECLARE + partition_date varchar; + from_ts bigint; + to_ts bigint; + key_cursor CURSOR FOR select SUBSTRING(month_date.first_date, 1, 7) AS partition_date, + extract(epoch from (month_date.first_date)::timestamp) * 1000 as from_ts, + extract(epoch from (month_date.first_date::date + INTERVAL '1 MONTH')::timestamp) * + 1000 as to_ts + FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_MM_01') AS first_date + FROM ts_kv_old) AS month_date; +BEGIN + OPEN key_cursor; + LOOP + FETCH key_cursor INTO partition_date, from_ts, to_ts; + EXIT WHEN NOT FOUND; + EXECUTE 'CREATE TABLE IF NOT EXISTS ts_kv_' || partition_date || + ' PARTITION OF ts_kv(PRIMARY KEY (entity_id, key, ts)) FOR VALUES FROM (' || from_ts || + ') TO (' || to_ts || ');'; + RAISE NOTICE 'A partition % has been created!',CONCAT('ts_kv_', partition_date); + END LOOP; + + CLOSE key_cursor; +END; +$$ language 'plpgsql'; + +-- load function create_ts_kv_dictionary_table() + +CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ + +BEGIN + CREATE TABLE IF NOT EXISTS ts_kv_dictionary + ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) + ); +END; +$$ LANGUAGE 'plpgsql'; + +-- load function insert_into_dictionary() + +CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS +$$ +DECLARE + insert_record RECORD; + key_cursor CURSOR FOR SELECT DISTINCT key + FROM ts_kv_old + ORDER BY key; +BEGIN + OPEN key_cursor; + LOOP + FETCH key_cursor INTO insert_record; + EXIT WHEN NOT FOUND; + IF NOT EXISTS(SELECT key FROM ts_kv_dictionary WHERE key = insert_record.key) THEN + INSERT INTO ts_kv_dictionary(key) VALUES (insert_record.key); + RAISE NOTICE 'Key: % has been inserted into the dictionary!',insert_record.key; + ELSE + RAISE NOTICE 'Key: % already exists in the dictionary!',insert_record.key; + END IF; + END LOOP; + CLOSE key_cursor; +END; +$$ language 'plpgsql'; + +-- load function insert_into_ts_kv() + +CREATE OR REPLACE FUNCTION insert_into_ts_kv() RETURNS void AS +$$ +DECLARE + insert_size CONSTANT integer := 10000; + insert_counter integer DEFAULT 0; + insert_record RECORD; + insert_cursor CURSOR FOR SELECT CONCAT(first, '-', second, '-1', third, '-', fourth, '-', fifth)::uuid AS entity_id, + substrings.key AS key, + substrings.ts AS ts, + substrings.bool_v AS bool_v, + substrings.str_v AS str_v, + substrings.long_v AS long_v, + substrings.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first, + SUBSTRING(entity_id, 4, 4) AS second, + SUBSTRING(entity_id, 1, 3) AS third, + SUBSTRING(entity_id, 16, 4) AS fourth, + SUBSTRING(entity_id, 20) AS fifth, + key_id AS key, + ts, + bool_v, + str_v, + long_v, + dbl_v + FROM ts_kv_old + INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS substrings; +BEGIN + OPEN insert_cursor; + LOOP + insert_counter := insert_counter + 1; + FETCH insert_cursor INTO insert_record; + IF NOT FOUND THEN + RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter - 1; + EXIT; + END IF; + INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) + VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, + insert_record.long_v, insert_record.dbl_v); + IF MOD(insert_counter, insert_size) = 0 THEN + RAISE NOTICE '% records have been inserted into the partitioned ts_kv!',insert_counter; + END IF; + END LOOP; + CLOSE insert_cursor; +END; +$$ LANGUAGE 'plpgsql'; + + 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 f424e262eb..78906e855e 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -23,7 +23,8 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.install.DatabaseUpgradeService; +import org.thingsboard.server.service.install.DatabaseTsUpgradeService; +import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService; import org.thingsboard.server.service.install.EntityDatabaseSchemaService; import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.TsDatabaseSchemaService; @@ -50,7 +51,10 @@ public class ThingsboardInstallService { private TsDatabaseSchemaService tsDatabaseSchemaService; @Autowired - private DatabaseUpgradeService databaseUpgradeService; + private DatabaseEntitiesUpgradeService databaseEntitiesUpgradeService; + + @Autowired + private DatabaseTsUpgradeService databaseTsUpgradeService; @Autowired private ComponentDiscoveryService componentDiscoveryService; @@ -73,48 +77,48 @@ public class ThingsboardInstallService { case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion log.info("Upgrading ThingsBoard from version 1.2.3 to 1.3.0 ..."); - databaseUpgradeService.upgradeDatabase("1.2.3"); + databaseEntitiesUpgradeService.upgradeDatabase("1.2.3"); case "1.3.0": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion log.info("Upgrading ThingsBoard from version 1.3.0 to 1.3.1 ..."); - databaseUpgradeService.upgradeDatabase("1.3.0"); + databaseEntitiesUpgradeService.upgradeDatabase("1.3.0"); case "1.3.1": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion log.info("Upgrading ThingsBoard from version 1.3.1 to 1.4.0 ..."); - databaseUpgradeService.upgradeDatabase("1.3.1"); + databaseEntitiesUpgradeService.upgradeDatabase("1.3.1"); case "1.4.0": log.info("Upgrading ThingsBoard from version 1.4.0 to 2.0.0 ..."); - databaseUpgradeService.upgradeDatabase("1.4.0"); + databaseEntitiesUpgradeService.upgradeDatabase("1.4.0"); dataUpdateService.updateData("1.4.0"); case "2.0.0": log.info("Upgrading ThingsBoard from version 2.0.0 to 2.1.1 ..."); - databaseUpgradeService.upgradeDatabase("2.0.0"); + databaseEntitiesUpgradeService.upgradeDatabase("2.0.0"); case "2.1.1": log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ..."); - databaseUpgradeService.upgradeDatabase("2.1.1"); + databaseEntitiesUpgradeService.upgradeDatabase("2.1.1"); case "2.1.3": log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ..."); - databaseUpgradeService.upgradeDatabase("2.1.3"); + databaseEntitiesUpgradeService.upgradeDatabase("2.1.3"); case "2.3.0": log.info("Upgrading ThingsBoard from version 2.3.0 to 2.3.1 ..."); - databaseUpgradeService.upgradeDatabase("2.3.0"); + databaseEntitiesUpgradeService.upgradeDatabase("2.3.0"); case "2.3.1": log.info("Upgrading ThingsBoard from version 2.3.1 to 2.4.0 ..."); - databaseUpgradeService.upgradeDatabase("2.3.1"); + databaseEntitiesUpgradeService.upgradeDatabase("2.3.1"); case "2.4.0": log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ..."); @@ -122,11 +126,16 @@ public class ThingsboardInstallService { case "2.4.1": log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); - databaseUpgradeService.upgradeDatabase("2.4.1"); + databaseEntitiesUpgradeService.upgradeDatabase("2.4.1"); case "2.4.2": log.info("Upgrading ThingsBoard from version 2.4.2 to 2.4.3 ..."); - databaseUpgradeService.upgradeDatabase("2.4.2"); + databaseEntitiesUpgradeService.upgradeDatabase("2.4.2"); + + case "2.4.3": + log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); + + databaseTsUpgradeService.upgradeDatabase("2.4.3"); log.info("Updating system data..."); 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 285a191c04..7e05179be0 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 @@ -59,7 +59,7 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE; @NoSqlDao @Profile("install") @Slf4j -public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { +public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java new file mode 100644 index 0000000000..7abb97a6a9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseEntitiesUpgradeService.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 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.install; + +public interface DatabaseEntitiesUpgradeService { + + void upgradeDatabase(String fromVersion) throws Exception; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/install/DatabaseUpgradeService.java rename to application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java index 47a3944e74..fe82cabff9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseTsUpgradeService.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.install; -public interface DatabaseUpgradeService { +public interface DatabaseTsUpgradeService { void upgradeDatabase(String fromVersion) throws Exception; -} +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/HsqlTsDatabaseSchemaService.java similarity index 80% rename from application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java rename to application/src/main/java/org/thingsboard/server/service/install/HsqlTsDatabaseSchemaService.java index 1288b0d652..3c877ca636 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/HsqlTsDatabaseSchemaService.java @@ -17,14 +17,16 @@ package org.thingsboard.server.service.install; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @Service @SqlTsDao +@HsqlDao @Profile("install") -public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService +public class HsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { - public SqlTsDatabaseSchemaService() { - super("schema-ts.sql", null); + public HsqlTsDatabaseSchemaService() { + super("schema-ts-hsql.sql", null); } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java new file mode 100644 index 0000000000..15b1e45247 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 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.install; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +@Service +@SqlTsDao +@PsqlDao +@Profile("install") +public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService + implements TsDatabaseSchemaService { + public PsqlTsDatabaseSchemaService() { + super("schema-ts-psql.sql", null); + } +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java new file mode 100644 index 0000000000..10b1e45231 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -0,0 +1,145 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Types; + +@Service +@Profile("install") +@Slf4j +@SqlTsDao +@PsqlDao +public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { + + private static final String CALL_REGEX = "call "; + private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; + private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; + private static final String CREATE_PARTITION_TABLE = CALL_REGEX + "create_partition_table()"; + private static final String CREATE_PARTITIONS = CALL_REGEX + "create_partitions()"; + private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; + private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; + private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_ts_kv()"; + private static final String DROP_OLD_TABLE = "DROP TABLE ts_kv_old;"; + + @Value("${spring.datasource.url}") + private String dbUrl; + + @Value("${spring.datasource.username}") + private String dbUserName; + + @Value("${spring.datasource.password}") + private String dbPassword; + + @Autowired + private InstallScripts installScripts; + + @Override + public void upgradeDatabase(String fromVersion) throws Exception { + switch (fromVersion) { + case "2.4.3": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating timeseries schema ..."); + log.info("Load upgrade functions ..."); + loadSql(conn); + log.info("Upgrade functions successfully loaded!"); + boolean versionValid = checkVersion(conn); + if (!versionValid) { + log.info("PostgreSQL version should be at least more than 10!"); + log.info("Please upgrade your PostgreSQL and restart the script!"); + } else { + log.info("PostgreSQL version is valid!"); + log.info("Updating schema ..."); + executeFunction(conn, CREATE_PARTITION_TABLE); + executeFunction(conn, CREATE_PARTITIONS); + executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); + executeFunction(conn, INSERT_INTO_DICTIONARY); + executeFunction(conn, INSERT_INTO_TS_KV); + dropOldTable(conn, DROP_OLD_TABLE); + log.info("schema timeseries updated!"); + } + } + break; + default: + throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); + } + } + + private void loadSql(Connection conn) { + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); + try { + loadFunctions(schemaUpdateFile, conn); + } catch (Exception e) { + log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage()); + } + } + + private void loadFunctions(Path sqlFile, Connection conn) throws Exception { + String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } + + private boolean checkVersion(Connection conn) { + log.info("Check the current PostgreSQL version..."); + boolean versionValid = false; + try { + CallableStatement callableStatement = conn.prepareCall("{? = " + CHECK_VERSION + " }"); + callableStatement.registerOutParameter(1, Types.BOOLEAN); + callableStatement.execute(); + versionValid = callableStatement.getBoolean(1); + callableStatement.close(); + } catch (Exception e) { + log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); + } + return versionValid; + } + + private void executeFunction(Connection conn, String query) { + log.info("{} ... ", query); + try { + CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); + callableStatement.execute(); + callableStatement.close(); + log.info("Successfully executed: {}", query.replace(CALL_REGEX, "")); + } catch (Exception e) { + log.info("Failed to execute {} due to: {}", query, e.getMessage()); + } + } + + private void dropOldTable(Connection conn, String query) { + try { + conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + Thread.sleep(5000); + } catch (InterruptedException | SQLException e) { + log.info("Failed to drop table {} due to: {}", query.replace("DROP TABLE ", ""), e.getMessage()); + } + } +} \ 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 b855b09354..d018c7fcef 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 @@ -54,7 +54,7 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE; @Profile("install") @Slf4j @SqlDao -public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { +public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { private static final String SCHEMA_UPDATE_SQL = "schema_update.sql"; @@ -172,7 +172,8 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { loadSql(schemaUpdateFile, conn); try { conn.createStatement().execute("ALTER TABLE device ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) {} + } catch (Exception e) { + } log.info("Schema updated."); } break; @@ -201,7 +202,8 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Updating schema ..."); try { conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_relation_types varchar"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) {} + } catch (Exception e) { + } log.info("Schema updated."); } break; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f374e1871e..8b934cd48f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -171,7 +171,7 @@ cassandra: read_consistency_level: "${CASSANDRA_READ_CONSISTENCY_LEVEL:ONE}" write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}" default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}" - # Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS,INDEFINITE + # Specify partitioning size for timestamp key-value storage. Example: MINUTES, HOURS, DAYS, MONTHS,INDEFINITE ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" ts_key_value_ttl: "${TS_KV_TTL:0}" events_ttl: "${TS_EVENTS_TTL:0}" @@ -214,6 +214,8 @@ sql: 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}" + # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE + ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" # Actor system parameters actors: diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index af839fea94..4fe33e4716 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -30,7 +30,7 @@ public class ControllerSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 9f3c2406f3..5fb8c4d0c7 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -29,7 +29,7 @@ public class MqttSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java index f30cd661c4..ce2c6852be 100644 --- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java @@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java index 49777cea15..3cbb7d9773 100644 --- a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java @@ -31,7 +31,7 @@ public class SystemSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java new file mode 100644 index 0000000000..b795ce451c --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2020 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.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +@ConditionalOnExpression("('${database.ts.type}'=='sql' || '${database.entities.type}'=='timescale') " + + "&& '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'") +public @interface PsqlTsAnyDao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java similarity index 74% rename from dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java rename to dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java index dea9c73f75..833c3745b9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/SqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java @@ -21,15 +21,17 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @Configuration @EnableAutoConfiguration -@ComponentScan("org.thingsboard.server.dao.sqlts.ts") -@EnableJpaRepositories("org.thingsboard.server.dao.sqlts.ts") -@EntityScan("org.thingsboard.server.dao.model.sqlts.ts") +@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.hsql", "org.thingsboard.server.dao.model.sqlts.latest"}) @EnableTransactionManagement @SqlTsDao -public class SqlTsDaoConfig { +@HsqlDao +public class HsqlTsDaoConfig { } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java new file mode 100644 index 0000000000..e3caf5e3d3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2020 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; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.domain.EntityScan; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +@Configuration +@EnableAutoConfiguration +@ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.psql", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) +@EnableTransactionManagement +@SqlTsDao +@PsqlDao +public class PsqlTsDaoConfig { + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java index bcb6ae7107..f2aa68c8db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java @@ -25,9 +25,9 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; @Configuration @EnableAutoConfiguration -@ComponentScan("org.thingsboard.server.dao.sqlts.timescale") -@EnableJpaRepositories("org.thingsboard.server.dao.sqlts.timescale") -@EntityScan("org.thingsboard.server.dao.model.sqlts.timescale") +@ComponentScan({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.latest"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.dictionary", "org.thingsboard.server.dao.sqlts.latest"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale", "org.thingsboard.server.dao.model.sqlts.dictionary", "org.thingsboard.server.dao.model.sqlts.latest"}) @EnableTransactionManagement @TimescaleDBTsDao public class TimescaleDaoConfig { diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java index b92b7613dd..a2dc184936 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java @@ -41,7 +41,7 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.nosql.AuditLogEntity; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTimeDao; -import org.thingsboard.server.dao.timeseries.TsPartitionDate; +import org.thingsboard.server.dao.timeseries.NoSqlTsPartitionDate; import org.thingsboard.server.dao.util.NoSqlDao; import javax.annotation.Nullable; @@ -92,7 +92,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao partition = TsPartitionDate.parse(partitioning); + Optional partition = NoSqlTsPartitionDate.parse(partitioning); if (partition.isPresent()) { tsFormat = partition.get(); } else { 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 b69e7c29d7..a07c4868e6 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 @@ -359,6 +359,7 @@ public class ModelConstants { public static final String PARTITION_COLUMN = "partition"; public static final String KEY_COLUMN = "key"; + public static final String KEY_ID_COLUMN = "key_id"; public static final String TS_COLUMN = "ts"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index 1f4469edd2..d1a8f9c462 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -16,11 +16,6 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; -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 javax.persistence.Column; import javax.persistence.Id; @@ -28,10 +23,9 @@ import javax.persistence.MappedSuperclass; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; -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 @@ -43,12 +37,8 @@ public abstract class AbstractTsKvEntity { protected static final String MAX = "MAX"; @Id - @Column(name = ENTITY_ID_COLUMN) - protected String entityId; - - @Id - @Column(name = KEY_COLUMN) - protected String key; + @Column(name = TS_COLUMN) + protected Long ts; @Column(name = BOOLEAN_VALUE_COLUMN) protected Boolean booleanValue; @@ -62,20 +52,6 @@ public abstract class AbstractTsKvEntity { @Column(name = DOUBLE_VALUE_COLUMN) protected Double doubleValue; - protected KvEntry getKvEntry() { - 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 kvEntry; - } - public abstract boolean isNotEmpty(); protected static boolean isAllNull(Object... args) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java new file mode 100644 index 0000000000..c324051a8d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 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.model.sqlts.dictionary; + +import lombok.Data; +import org.hibernate.annotations.Generated; +import org.hibernate.annotations.GenerationTime; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.Table; + +import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.KEY_ID_COLUMN; + +@Data +@Entity +@Table(name = "ts_kv_dictionary") +@IdClass(TsKvDictionaryCompositeKey.class) +public final class TsKvDictionary { + + @Id + @Column(name = KEY_COLUMN) + private String key; + + @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="serial") + @Generated(GenerationTime.INSERT) + private int keyId; + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java new file mode 100644 index 0000000000..064f5ce46c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionaryCompositeKey.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 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.model.sqlts.dictionary; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.Transient; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TsKvDictionaryCompositeKey implements Serializable{ + + @Transient + private static final long serialVersionUID = -4089175869616037523L; + + private String key; +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java index 7eba9a9041..0d1b7f57a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.ts; +package org.thingsboard.server.dao.model.sqlts.hsql; import lombok.AllArgsConstructor; import lombok.Data; @@ -35,4 +35,5 @@ public class TsKvCompositeKey implements Serializable { private String entityId; private String key; private long ts; + } \ 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/hsql/TsKvEntity.java similarity index 78% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java index ba12b5d5cd..97e68655ad 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/hsql/TsKvEntity.java @@ -13,11 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.ts; +package org.thingsboard.server.dao.model.sqlts.hsql; 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.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 org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; @@ -30,8 +35,9 @@ import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; @Data @Entity @@ -45,8 +51,12 @@ public final class TsKvEntity extends AbstractTsKvEntity implements ToData { + + @Id + @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") + protected UUID entityId; + + @Id + @Column(name = KEY_COLUMN) + protected int key; + + @Transient + protected String strKey; + + public TsKvEntity() { + } + + public TsKvEntity(String strValue) { + this.strValue = strValue; + } + + public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { + if (!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) { + switch (aggType) { + case AVG: + double sum = 0.0; + if (longValue != null) { + sum += longValue; + } + if (doubleValue != null) { + sum += doubleValue; + } + long totalCount = longCountValue + doubleCountValue; + if (totalCount > 0) { + this.doubleValue = sum / (longCountValue + doubleCountValue); + } else { + this.doubleValue = 0.0; + } + break; + case SUM: + if (doubleCountValue > 0) { + this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); + } else { + this.longValue = longValue; + } + break; + case MIN: + case MAX: + if (longCountValue > 0 && doubleCountValue > 0) { + this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); + } else if (doubleCountValue > 0) { + this.doubleValue = doubleValue; + } else if (longCountValue > 0) { + this.longValue = longValue; + } + break; + } + } + } + + public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { + if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { + if (booleanValueCount != 0) { + this.longValue = booleanValueCount; + } else if (strValueCount != 0) { + this.longValue = strValueCount; + } else { + this.longValue = longValueCount + doubleValueCount; + } + } + } + + @Override + public boolean isNotEmpty() { + return strValue != null || longValue != null || doubleValue != null || booleanValue != null; + } + + @Override + public TsKvEntry toData() { + KvEntry kvEntry = null; + if (strValue != null) { + kvEntry = new StringDataEntry(strKey, strValue); + } else if (longValue != null) { + kvEntry = new LongDataEntry(strKey, longValue); + } else if (doubleValue != null) { + kvEntry = new DoubleDataEntry(strKey, doubleValue); + } else if (booleanValue != null) { + kvEntry = new BooleanDataEntry(strKey, booleanValue); + } + return new BasicTsKvEntry(ts, kvEntry); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java index 49db8554c9..8209b4a77f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java @@ -21,6 +21,7 @@ import lombok.NoArgsConstructor; import javax.persistence.Transient; import java.io.Serializable; +import java.util.UUID; @Data @AllArgsConstructor @@ -30,8 +31,8 @@ public class TimescaleTsKvCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -4089175869616037523L; - private String tenantId; - private String entityId; - private String key; + private UUID tenantId; + private UUID entityId; + private int key; private long ts; } \ No newline at end of file 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 a02f030701..626b1104f4 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 @@ -19,6 +19,11 @@ 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.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 org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; @@ -34,9 +39,12 @@ import javax.persistence.NamedNativeQuery; import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMappings; import javax.persistence.Table; +import javax.persistence.Transient; +import java.util.UUID; +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.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; @@ -118,21 +126,30 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToData { @Id - @Column(name = TENANT_ID_COLUMN) - private String tenantId; + @Column(name = TENANT_ID_COLUMN, columnDefinition = "uuid") + private UUID tenantId; @Id - @Column(name = TS_COLUMN) - protected Long ts; + @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") + protected UUID entityId; - public TimescaleTsKvEntity() { } + @Id + @Column(name = KEY_COLUMN) + protected int key; + + @Transient + protected String strKey; + + + public TimescaleTsKvEntity() { + } public TimescaleTsKvEntity(Long tsBucket, Long interval, Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String strValue, String aggType) { if (!StringUtils.isEmpty(strValue)) { this.strValue = strValue; } if (!isAllNull(tsBucket, interval, longValue, doubleValue, longCountValue, doubleCountValue)) { - this.ts = tsBucket + interval/2; + this.ts = tsBucket + interval / 2; switch (aggType) { case AVG: double sum = 0.0; @@ -172,7 +189,7 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToD public TimescaleTsKvEntity(Long tsBucket, Long interval, Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { if (!isAllNull(tsBucket, interval, booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { - this.ts = tsBucket + interval/2; + this.ts = tsBucket + interval / 2; if (booleanValueCount != 0) { this.longValue = booleanValueCount; } else if (strValueCount != 0) { @@ -190,6 +207,17 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToD @Override public TsKvEntry toData() { - return new BasicTsKvEntry(ts, getKvEntry()); + KvEntry kvEntry = null; + if (strValue != null) { + kvEntry = new StringDataEntry(strKey, strValue); + } else if (longValue != null) { + kvEntry = new LongDataEntry(strKey, longValue); + } else if (doubleValue != null) { + kvEntry = new DoubleDataEntry(strKey, doubleValue); + } else if (booleanValue != null) { + kvEntry = new BooleanDataEntry(strKey, booleanValue); + } + return new BasicTsKvEntry(ts, kvEntry); } + } \ No newline at end of file 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 1d87674231..dd55137f81 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 @@ -15,13 +15,8 @@ */ 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 { @Autowired 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 f299717bf2..7c40ad8421 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 @@ -21,8 +21,6 @@ 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; import java.util.regex.Pattern; @Repository @@ -31,64 +29,15 @@ 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"; - 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"; - @Value("${sql.remove_null_chars}") private boolean removeNullChars; - @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 + ")"; - } - - 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 + "]"); - } - } - protected String replaceNullChars(String strValue) { if (removeNullChars && strValue != null) { return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); 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 deleted file mode 100644 index ff984a43ed..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractLatestInsertRepository.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright © 2016-2020 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; - -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); - } - 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/AbstractSimpleSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java new file mode 100644 index 0000000000..a26eccbf06 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java @@ -0,0 +1,156 @@ +/** + * Copyright © 2016-2020 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 com.google.common.util.concurrent.Futures; +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.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractSimpleSqlTimeseriesDao extends AbstractSqlTimeseriesDao { + + @Autowired + private InsertTsRepository insertRepository; + + @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; + + protected TbSqlBlockingQueue> tsQueue; + + @PostConstruct + protected void init() { + super.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)); + } + + @PreDestroy + protected void destroy() { + super.init(); + if (tsQueue != null) { + tsQueue.destroy(); + } + } + + protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + if (query.getAggregation() == Aggregation.NONE) { + return findAllAsyncWithLimit(entityId, query); + } else { + long stepTs = query.getStartTs(); + List>> futures = new ArrayList<>(); + while (stepTs < query.getEndTs()) { + long startTs = stepTs; + long endTs = stepTs + query.getInterval(); + long ts = startTs + (endTs - startTs) / 2; + futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); + stepTs = endTs; + } + return getTskvEntriesFuture(Futures.allAsList(futures)); + } + } + + protected abstract ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); + + protected abstract ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query); + + protected SettableFuture setFutures(List> entitiesFutures) { + SettableFuture listenableFuture = SettableFuture.create(); + CompletableFuture> entities = + CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) + .thenApply(v -> entitiesFutures.stream() + .map(CompletableFuture::join) + .collect(Collectors.toList())); + + entities.whenComplete((tsKvEntities, throwable) -> { + if (throwable != null) { + listenableFuture.setException(throwable); + } else { + T result = null; + for (T entity : tsKvEntities) { + if (entity.isNotEmpty()) { + result = entity; + break; + } + } + listenableFuture.set(result); + } + }); + return listenableFuture; + } + + protected void switchAgregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + switch (aggregation) { + case AVG: + findAvg(entityId, key, startTs, endTs, entitiesFutures); + break; + case MAX: + findMax(entityId, key, startTs, endTs, entitiesFutures); + break; + case MIN: + findMin(entityId, key, startTs, endTs, entitiesFutures); + break; + case SUM: + findSum(entityId, key, startTs, endTs, entitiesFutures); + break; + case COUNT: + findCount(entityId, key, startTs, endTs, entitiesFutures); + break; + default: + throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); + } + } + + protected abstract void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); +} \ No newline at end of file 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 45f477a448..7f340cc48f 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 @@ -16,20 +16,32 @@ package org.thingsboard.server.dao.sqlts; import com.google.common.base.Function; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; +import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; -import org.thingsboard.server.dao.timeseries.TsInsertExecutorType; +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.latest.TsKvLatestRepository; +import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -37,13 +49,55 @@ import javax.annotation.PreDestroy; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.Executors; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; + +@Slf4j public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningExecutorService { private static final String DESC_ORDER = "DESC"; + @Autowired + private TsKvLatestRepository tsKvLatestRepository; + + @Autowired + private InsertLatestRepository insertLatestRepository; + + @Autowired + protected ScheduledLogExecutorComponent logExecutor; + + @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 tsLatestQueue; + + @PostConstruct + protected void init() { + 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 + protected void destroy() { + if (tsLatestQueue != null) { + tsLatestQueue.destroy(); + } + } + protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() @@ -89,4 +143,105 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx Aggregation.NONE, DESC_ORDER); return findAllAsync(tenantId, entityId, findNewLatestQuery); } + + protected ListenableFuture getFindLatestFuture(EntityId entityId, String key) { + TsKvLatestCompositeKey compositeKey = + new TsKvLatestCompositeKey( + entityId.getEntityType(), + fromTimeUUID(entityId.getId()), + key); + Optional entry = tsKvLatestRepository.findById(compositeKey); + TsKvEntry result; + if (entry.isPresent()) { + result = DaoUtil.getData(entry.get()); + } else { + result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); + } + return Futures.immediateFuture(result); + } + + protected ListenableFuture getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + ListenableFuture latestFuture = getFindLatestFuture(entityId, query.getKey()); + + ListenableFuture booleanFuture = Futures.transform(latestFuture, tsKvEntry -> { + long ts = tsKvEntry.getTs(); + return ts > query.getStartTs() && ts <= query.getEndTs(); + }, service); + + ListenableFuture removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { + if (isRemove) { + TsKvLatestEntity latestEntity = new TsKvLatestEntity(); + latestEntity.setEntityType(entityId.getEntityType()); + latestEntity.setEntityId(fromTimeUUID(entityId.getId())); + latestEntity.setKey(query.getKey()); + return service.submit(() -> { + tsKvLatestRepository.delete(latestEntity); + return null; + }); + } + return Futures.immediateFuture(null); + }, service); + + final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>(); + Futures.addCallback(removedLatestFuture, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + if (query.getRewriteLatestIfDeleted()) { + ListenableFuture savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { + if (isRemove) { + return getNewLatestEntryFuture(tenantId, entityId, query); + } + return Futures.immediateFuture(null); + }, service); + + try { + resultFuture.set(savedLatestFuture.get()); + } catch (InterruptedException | ExecutionException e) { + log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e); + } + } else { + resultFuture.set(null); + } + } + + @Override + public void onFailure(Throwable t) { + log.warn("[{}] Failed to process remove of the latest value", entityId, t); + } + }); + return resultFuture; + } + + protected ListenableFuture> getFindAllLatestFuture(EntityId entityId) { + return Futures.immediateFuture( + DaoUtil.convertDataList(Lists.newArrayList( + tsKvLatestRepository.findAllByEntityTypeAndEntityId( + entityId.getEntityType(), + UUIDConverter.fromTimeUUID(entityId.getId()))))); + } + + protected ListenableFuture getSaveLatestFuture(EntityId entityId, TsKvEntry tsKvEntry) { + TsKvLatestEntity latestEntity = new TsKvLatestEntity(); + latestEntity.setEntityType(entityId.getEntityType()); + latestEntity.setEntityId(fromTimeUUID(entityId.getId())); + latestEntity.setTs(tsKvEntry.getTs()); + latestEntity.setKey(tsKvEntry.getKey()); + latestEntity.setStrValue(tsKvEntry.getStrValue().orElse(null)); + latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); + latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); + latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + return tsLatestQueue.add(latestEntity); + } + + private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); + return Futures.transformAsync(future, entryList -> { + if (entryList.size() == 1) { + return getSaveLatestFuture(entityId, entryList.get(0)); + } else { + log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey()); + } + return Futures.immediateFuture(null); + }, service); + } } 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 deleted file mode 100644 index 8a387522f8..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractTimeseriesInsertRepository.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright © 2016-2020 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.sql.AbstractTsKvEntity; - -import java.util.List; - -@Repository -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); - } - 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(T entity, String query); - - @Modifying - protected abstract void saveOrUpdateString(T entity, String query); - - @Modifying - protected abstract void saveOrUpdateLong(T entity, String query); - - @Modifying - protected abstract void saveOrUpdateDouble(T entity, String query); - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/EntityContainer.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/EntityContainer.java new file mode 100644 index 0000000000..3422f34a53 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/EntityContainer.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2020 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 lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; + +@Data +@AllArgsConstructor +public class EntityContainer { + + private T entity; + private String partitionDate; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java new file mode 100644 index 0000000000..1e1aede157 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 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.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; + +import java.util.List; + +public interface InsertLatestRepository { + + void saveOrUpdate(List entities); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java new file mode 100644 index 0000000000..6ab11618f0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 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.thingsboard.server.dao.model.sql.AbstractTsKvEntity; + +import java.util.List; + +public interface InsertTsRepository { + + void saveOrUpdate(List> entities); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java new file mode 100644 index 0000000000..60284c73cf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 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.dictionary; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; +import org.thingsboard.server.dao.util.PsqlDao; + +import java.util.Optional; + +@PsqlDao +public interface TsKvDictionaryRepository extends CrudRepository { + + Optional findByKeyId(int keyId); + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java new file mode 100644 index 0000000000..5cc344aa2a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016-2020 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.hsql; + +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; +import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.InsertTsRepository; +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 +@HsqlDao +@Repository +@Transactional +public class HsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository { + + 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(List> entities) { + jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + EntityContainer tsKvEntityEntityContainer = entities.get(i); + TsKvEntity tsKvEntity = tsKvEntityEntityContainer.getEntity(); + ps.setString(1, tsKvEntity.getEntityType().name()); + ps.setString(2, tsKvEntity.getEntityId()); + ps.setString(3, tsKvEntity.getKey()); + ps.setLong(4, tsKvEntity.getTs()); + + if (tsKvEntity.getBooleanValue() != null) { + ps.setBoolean(5, tsKvEntity.getBooleanValue()); + } else { + ps.setNull(5, Types.BOOLEAN); + } + + ps.setString(6, tsKvEntity.getStrValue()); + + if (tsKvEntity.getLongValue() != null) { + ps.setLong(7, tsKvEntity.getLongValue()); + } else { + ps.setNull(7, Types.BIGINT); + } + + if (tsKvEntity.getDoubleValue() != null) { + ps.setDouble(8, tsKvEntity.getDoubleValue()); + } else { + ps.setNull(8, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java new file mode 100644 index 0000000000..c8bafe81e0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -0,0 +1,206 @@ +/** + * Copyright © 2016-2020 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.hsql; + +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.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; +import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.timeseries.TimeseriesDao; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; + + +@Component +@Slf4j +@SqlTsDao +@HsqlDao +public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao implements TimeseriesDao { + + @Autowired + private TsKvHsqlRepository tsKvRepository; + + @Override + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + return processFindAllAsync(tenantId, entityId, queries); + } + + @Override + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + TsKvEntity entity = new TsKvEntity(); + entity.setEntityType(entityId.getEntityType()); + entity.setEntityId(fromTimeUUID(entityId.getId())); + entity.setTs(tsKvEntry.getTs()); + entity.setKey(tsKvEntry.getKey()); + entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); + entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); + entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); + entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + log.trace("Saving entity: {}", entity); + return tsQueue.add(new EntityContainer(entity, null)); + } + + @Override + public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return service.submit(() -> { + tsKvRepository.delete( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + query.getKey(), + query.getStartTs(), + query.getEndTs()); + return null; + }); + } + + @Override + public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { + return getSaveLatestFuture(entityId, tsKvEntry); + } + + @Override + public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return getRemoveLatestFuture(tenantId, entityId, query); + } + + @Override + public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { + return getFindLatestFuture(entityId, key); + } + + @Override + public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { + return getFindAllLatestFuture(entityId); + } + + @Override + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + return Futures.immediateFuture(null); + } + + @Override + public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return Futures.immediateFuture(null); + } + + protected ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + List> entitiesFutures = new ArrayList<>(); + switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); + return Futures.transform(setFutures(entitiesFutures), entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(fromTimeUUID(entityId.getId())); + entity.setEntityType(entityId.getEntityType()); + entity.setKey(key); + entity.setTs(ts); + return Optional.of(DaoUtil.getData(entity)); + } else { + return Optional.empty(); + } + }); + } + + protected ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { + return Futures.immediateFuture( + DaoUtil.convertDataList( + tsKvRepository.findAllWithLimit( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + query.getKey(), + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), "ts"))))); + } + + protected void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + entitiesFutures.add(tsKvRepository.findCount( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + } + + protected void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + entitiesFutures.add(tsKvRepository.findSum( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + } + + protected void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + entitiesFutures.add(tsKvRepository.findStringMin( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMin( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + } + + protected void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + entitiesFutures.add(tsKvRepository.findStringMax( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMax( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + } + + protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + entitiesFutures.add(tsKvRepository.findAvg( + fromTimeUUID(entityId.getId()), + entityId.getEntityType(), + key, + startTs, + endTs)); + } + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java index 64a3b43574..f770b4dca9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.ts; +package org.thingsboard.server.dao.sqlts.hsql; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Modifying; @@ -23,15 +23,15 @@ import org.springframework.data.repository.query.Param; import org.springframework.scheduling.annotation.Async; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvCompositeKey; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; +import org.thingsboard.server.dao.model.sqlts.hsql.TsKvCompositeKey; +import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; import java.util.concurrent.CompletableFuture; @SqlDao -public interface TsKvRepository extends CrudRepository { +public interface TsKvHsqlRepository extends CrudRepository { @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + "AND tskv.entityType = :entityType AND tskv.key = :entityKey " + @@ -146,4 +146,4 @@ public interface TsKvRepository extends CrudRepository 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(); + } + }); + } +} \ No newline at end of file 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/latest/PsqlLatestInsertRepository.java similarity index 64% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java index 16c0bb5426..bace8ff637 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.ts; +package org.thingsboard.server.dao.sqlts.latest; 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 org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; +import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.InsertLatestRepository; +import org.thingsboard.server.dao.util.PsqlTsAnyDao; import java.sql.PreparedStatement; import java.sql.SQLException; @@ -31,18 +31,11 @@ import java.sql.Types; import java.util.ArrayList; import java.util.List; -@SqlTsDao -@PsqlDao + +@PsqlTsAnyDao @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); +public class PsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { 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 = ?"; @@ -52,11 +45,6 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { "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() { @@ -160,48 +148,4 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository { } }); } - - @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", replaceNullChars(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/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java similarity index 83% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvLatestRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index ecefc2a86e..71c8a00057 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.ts; +package org.thingsboard.server.dao.sqlts.latest; import org.springframework.data.repository.CrudRepository; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestCompositeKey; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvLatestEntity; +import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; +import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java new file mode 100644 index 0000000000..fbaa2f9acf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -0,0 +1,312 @@ +/** + * Copyright © 2016-2020 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.psql; + +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.beans.factory.annotation.Value; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; +import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; +import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; +import org.thingsboard.server.dao.timeseries.PsqlPartition; +import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; +import org.thingsboard.server.dao.timeseries.TimeseriesDao; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; + +import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_START; + + +@Component +@Slf4j +@SqlTsDao +@PsqlDao +public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao implements TimeseriesDao { + + private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); + private final Set partitions = ConcurrentHashMap.newKeySet(); + + private static final ReentrantLock tsCreationLock = new ReentrantLock(); + private static final ReentrantLock partitionCreationLock = new ReentrantLock(); + + @Autowired + private TsKvDictionaryRepository dictionaryRepository; + + @Autowired + private TsKvPsqlRepository tsKvRepository; + + @Autowired + private PsqlPartitioningRepository partitioningRepository; + + private SqlTsPartitionDate tsFormat; + + @Value("${sql.ts_key_value_partitioning}") + private String partitioning; + + @Override + protected void init() { + super.init(); + Optional partition = SqlTsPartitionDate.parse(partitioning); + if (partition.isPresent()) { + tsFormat = partition.get(); + } else { + log.warn("Incorrect configuration of partitioning {}", partitioning); + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); + } + } + + @Override + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + return processFindAllAsync(tenantId, entityId, queries); + } + + @Override + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + String strKey = tsKvEntry.getKey(); + Integer keyId = getOrSaveKeyId(strKey); + TsKvEntity entity = new TsKvEntity(); + entity.setEntityId(entityId.getId()); + entity.setTs(tsKvEntry.getTs()); + entity.setKey(keyId); + entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); + entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); + entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); + entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + PsqlPartition psqlPartition = toPartition(tsKvEntry.getTs()); + savePartition(psqlPartition); + log.trace("Saving entity: {}", entity); + return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate())); + } + + @Override + public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return service.submit(() -> { + String strKey = query.getKey(); + Integer keyId = getOrSaveKeyId(strKey); + tsKvRepository.delete( + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs()); + return null; + }); + } + + @Override + public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return getRemoveLatestFuture(tenantId, entityId, query); + } + + @Override + public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { + return getSaveLatestFuture(entityId, tsKvEntry); + } + + @Override + public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { + return getFindLatestFuture(entityId, key); + } + + @Override + public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { + return getFindAllLatestFuture(entityId); + } + + @Override + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + return Futures.immediateFuture(null); + } + + @Override + public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return Futures.immediateFuture(null); + } + + protected ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + List> entitiesFutures = new ArrayList<>(); + switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); + return Futures.transform(setFutures(entitiesFutures), entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(entityId.getId()); + entity.setStrKey(key); + entity.setTs(ts); + return Optional.of(DaoUtil.getData(entity)); + } else { + return Optional.empty(); + } + }); + } + + protected ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { + Integer keyId = getOrSaveKeyId(query.getKey()); + List tsKvEntities = tsKvRepository.findAllWithLimit( + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), "ts"))); + tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); + return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); + } + + protected void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findCount( + entityId.getId(), + keyId, + startTs, + endTs)); + } + + protected void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findSum( + entityId.getId(), + keyId, + startTs, + endTs)); + } + + protected void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findStringMin( + entityId.getId(), + keyId, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMin( + entityId.getId(), + keyId, + startTs, + endTs)); + } + + protected void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findStringMax( + entityId.getId(), + keyId, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMax( + entityId.getId(), + keyId, + startTs, + endTs)); + } + + protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findAvg( + entityId.getId(), + keyId, + startTs, + endTs)); + } + + private Integer getOrSaveKeyId(String strKey) { + Integer keyId = tsKvDictionaryMap.get(strKey); + if (keyId == null) { + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + tsCreationLock.lock(); + try { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + TsKvDictionary tsKvDictionary = new TsKvDictionary(); + tsKvDictionary.setKey(strKey); + try { + TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); + tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (ConstraintViolationException e) { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); + tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + } + } finally { + tsCreationLock.unlock(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + tsKvDictionaryMap.put(strKey, keyId); + } + } + return keyId; + } + + private void savePartition(PsqlPartition psqlPartition) { + if (!partitions.contains(psqlPartition)) { + partitionCreationLock.lock(); + try { + log.trace("Saving partition: {}", psqlPartition); + partitioningRepository.save(psqlPartition); + log.trace("Adding partition to Set: {}", psqlPartition); + partitions.add(psqlPartition); + } finally { + partitionCreationLock.unlock(); + } + } + } + + private PsqlPartition toPartition(long ts) { + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); + LocalDateTime localDateTimeStart = tsFormat.trancateTo(time); + if (localDateTimeStart == SqlTsPartitionDate.EPOCH_START) { + return new PsqlPartition(toMills(EPOCH_START), Long.MAX_VALUE, tsFormat.getPattern()); + } else { + LocalDateTime localDateTimeEnd = tsFormat.plusTo(localDateTimeStart); + return new PsqlPartition(toMills(localDateTimeStart), toMills(localDateTimeEnd), tsFormat.getPattern()); + } + } + + private long toMills(LocalDateTime time) { return time.toInstant(ZoneOffset.UTC).toEpochMilli(); } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java new file mode 100644 index 0000000000..0e22cb26ba --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2020 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.psql; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.timeseries.PsqlPartition; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsDao; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@SqlTsDao +@PsqlDao +@Repository +@Transactional +public class PsqlPartitioningRepository { + + @PersistenceContext + private EntityManager entityManager; + + public void save(PsqlPartition partition) { + entityManager.createNativeQuery(partition.getQuery()) + .executeUpdate(); + } + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java new file mode 100644 index 0000000000..b1aaff4ec8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java @@ -0,0 +1,101 @@ +/** + * Copyright © 2016-2020 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.psql; + +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; +import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.InsertTsRepository; +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.HashMap; +import java.util.List; +import java.util.Map; + +@SqlTsDao +@PsqlDao +@Repository +@Transactional +public class PsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository { + + private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_"; + + private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES (?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + + @Override + public void saveOrUpdate(List> entities) { + Map> partitionMap = new HashMap<>(); + for (EntityContainer entityContainer : entities) { + List tsKvEntities = partitionMap.computeIfAbsent(entityContainer.getPartitionDate(), k -> new ArrayList<>()); + tsKvEntities.add(entityContainer.getEntity()); + } + partitionMap.forEach((partition, entries) -> jdbcTemplate.batchUpdate(getInsertOrUpdateQuery(partition), new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + TsKvEntity tsKvEntity = entries.get(i); + ps.setObject(1, tsKvEntity.getEntityId()); + ps.setInt(2, tsKvEntity.getKey()); + ps.setLong(3, tsKvEntity.getTs()); + + if (tsKvEntity.getBooleanValue() != null) { + ps.setBoolean(4, tsKvEntity.getBooleanValue()); + ps.setBoolean(8, tsKvEntity.getBooleanValue()); + } else { + ps.setNull(4, Types.BOOLEAN); + ps.setNull(8, Types.BOOLEAN); + } + + ps.setString(5, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(9, replaceNullChars(tsKvEntity.getStrValue())); + + + if (tsKvEntity.getLongValue() != null) { + ps.setLong(6, tsKvEntity.getLongValue()); + ps.setLong(10, tsKvEntity.getLongValue()); + } else { + ps.setNull(6, Types.BIGINT); + ps.setNull(10, Types.BIGINT); + } + + if (tsKvEntity.getDoubleValue() != null) { + ps.setDouble(7, tsKvEntity.getDoubleValue()); + ps.setDouble(11, tsKvEntity.getDoubleValue()); + } else { + ps.setNull(7, Types.DOUBLE); + ps.setNull(11, Types.DOUBLE); + } + } + + @Override + public int getBatchSize() { + return entries.size(); + } + })); + } + + private String getInsertOrUpdateQuery(String partitionDate) { + return INSERT_INTO_TS_KV + partitionDate + VALUES_ON_CONFLICT_DO_UPDATE; + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java new file mode 100644 index 0000000000..7b3328f86b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java @@ -0,0 +1,132 @@ +/** + * Copyright © 2016-2020 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.psql; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sqlts.psql.TsKvCompositeKey; +import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +@SqlDao +public interface TsKvPsqlRepository extends CrudRepository { + + @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + List findAllWithLimit(@Param("entityId") UUID entityId, + @Param("entityKey") int key, + @Param("startTs") long startTs, + @Param("endTs") long endTs, + Pageable pageable); + + @Transactional + @Modifying + @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + void delete(@Param("entityId") UUID entityId, + @Param("entityKey") int key, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + + "WHERE tskv.strValue IS NOT NULL " + + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findStringMax(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + + "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'MAX') FROM TsKvEntity tskv " + + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findNumericMax(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + + @Async + @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + + "WHERE tskv.strValue IS NOT NULL " + + "AND tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findStringMin(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + + "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'MIN') FROM TsKvEntity tskv " + + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findNumericMin( + @Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findCount(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'AVG') FROM TsKvEntity tskv " + + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findAvg(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + + @Async + @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + + "SUM(COALESCE(tskv.doubleValue, 0.0)), " + + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + + "'SUM') FROM TsKvEntity tskv " + + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findSum(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, + @Param("startTs") long startTs, + @Param("endTs") long endTs); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index da39086da8..15deb702a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -23,6 +23,7 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; @Repository @@ -36,7 +37,7 @@ public class AggregationRepository { public static final String FIND_COUNT = "findCount"; - public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS varchar) AND tskv.entity_id = cast(:entityId AS varchar) AND tskv.key= cast(:entityKey AS varchar) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket"; + public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket"; public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; @@ -52,41 +53,41 @@ public class AggregationRepository { private EntityManager entityManager; @Async - public CompletableFuture> findAvg(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findAvg(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findMax(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findMax(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findMin(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findMin(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findSum(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findSum(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findCount(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findCount(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT); return CompletableFuture.supplyAsync(() -> resultList); } - private List getResultList(String tenantId, String entityId, String entityKey, long timeBucket, long startTs, long endTs, String query) { + private List getResultList(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) { return entityManager.createNamedQuery(query) .setParameter("tenantId", tenantId) .setParameter("entityId", entityId) 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 2adaa045ac..35f0fd497a 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 @@ -19,7 +19,9 @@ import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractTimeseriesInsertRepository; +import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.InsertTsRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @@ -32,35 +34,21 @@ import java.util.List; @PsqlDao @Repository @Transactional -public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepository { - - 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); - - 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 = ?"; - +public class TimescaleInsertRepository extends AbstractInsertRepository implements InsertTsRepository { 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) { + public void saveOrUpdate(List> entities) { jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { - TimescaleTsKvEntity tsKvEntity = entities.get(i); - ps.setString(1, tsKvEntity.getTenantId()); - ps.setString(2, tsKvEntity.getEntityId()); - ps.setString(3, tsKvEntity.getKey()); + TimescaleTsKvEntity tsKvEntity = entities.get(i).getEntity(); + ps.setObject(1, tsKvEntity.getTenantId()); + ps.setObject(2, tsKvEntity.getEntityId()); + ps.setInt(3, tsKvEntity.getKey()); ps.setLong(4, tsKvEntity.getTs()); if (tsKvEntity.getBooleanValue() != null) { @@ -98,52 +86,4 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor } }); } - - @Override - protected void saveOrUpdateBoolean(TimescaleTsKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("tenant_id", entity.getTenantId()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("key", entity.getKey()) - .setParameter("ts", entity.getTs()) - .setParameter("bool_v", entity.getBooleanValue()) - .executeUpdate(); - } - - @Override - protected void saveOrUpdateString(TimescaleTsKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("tenant_id", entity.getTenantId()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("key", entity.getKey()) - .setParameter("ts", entity.getTs()) - .setParameter("str_v", replaceNullChars(entity.getStrValue())) - .executeUpdate(); - } - - @Override - protected void saveOrUpdateLong(TimescaleTsKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("tenant_id", entity.getTenantId()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("key", entity.getKey()) - .setParameter("ts", entity.getTs()) - .setParameter("long_v", entity.getLongValue()) - .executeUpdate(); - } - - @Override - protected void saveOrUpdateDouble(TimescaleTsKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("tenant_id", entity.getTenantId()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("key", entity.getKey()) - .setParameter("ts", entity.getTs()) - .setParameter("dbl_v", entity.getDoubleValue()) - .executeUpdate(); - } - - private static String getInsertOrUpdateString(String value, String nullValues) { - return "INSERT INTO tenant_ts_kv(tenant_id, entity_id, key, ts, " + value + ") VALUES (:tenant_id, :entity_id, :key, :ts, :" + value + ") ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET " + value + " = :" + value + ", ts = :ts," + nullValues; - } } \ No newline at end of file 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 9a2f534a59..6de9e3c51a 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 @@ -15,11 +15,11 @@ */ package org.thingsboard.server.dao.sqlts.timescale; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; 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.springframework.data.domain.PageRequest; @@ -29,19 +29,19 @@ import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.common.data.kv.StringDataEntry; 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.dictionary.TsKvDictionary; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; 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.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @@ -51,9 +51,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; - -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantLock; @Component @@ -63,17 +65,21 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements private static final String TS = "ts"; + private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); + + private static final ReentrantLock tsCreationLock = new ReentrantLock(); + @Autowired - private TsKvTimescaleRepository tsKvRepository; + private TsKvDictionaryRepository dictionaryRepository; @Autowired - private AggregationRepository aggregationRepository; + private TsKvTimescaleRepository tsKvRepository; @Autowired - private AbstractTimeseriesInsertRepository insertRepository; + private AggregationRepository aggregationRepository; @Autowired - ScheduledLogExecutorComponent logExecutor; + private InsertTsRepository insertRepository; @Value("${sql.ts_timescale.batch_size:1000}") private int batchSize; @@ -84,10 +90,11 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Value("${sql.ts_timescale.stats_print_interval_ms:1000}") private long statsPrintIntervalMs; - private TbSqlBlockingQueue queue; + private TbSqlBlockingQueue> queue; @PostConstruct - private void init() { + protected void init() { + super.init(); TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() .logName("TS Timescale") .batchSize(batchSize) @@ -99,17 +106,13 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @PreDestroy - private void destroy() { + protected void destroy() { + super.init(); if (queue != null) { queue.destroy(); } } - @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { - return processFindAllAsync(tenantId, entityId, queries); - } - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { return findAllAsyncWithLimit(tenantId, entityId, query); @@ -122,50 +125,36 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } } - private ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - return Futures.immediateFuture( - DaoUtil.convertDataList( - tsKvRepository.findAllWithLimit( - fromTimeUUID(tenantId.getId()), - fromTimeUUID(entityId.getId()), - query.getKey(), - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), "ts"))))); + @Override + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + return processFindAllAsync(tenantId, entityId, queries); } - @Override public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { - ListenableFuture> future = getLatest(tenantId, entityId, key, 0L, System.currentTimeMillis()); - return Futures.transform(future, latest -> { - if (!CollectionUtils.isEmpty(latest)) { - return DaoUtil.getData(latest.get(0)); - } else { - return new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); - } - }, service); + return getFindLatestFuture(entityId, key); } @Override public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { - return Futures.immediateFuture(DaoUtil.convertDataList(Lists.newArrayList(tsKvRepository.findAllLatestValues(fromTimeUUID(tenantId.getId()), fromTimeUUID(entityId.getId()))))); + return getFindAllLatestFuture(entityId); } @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + String strKey = tsKvEntry.getKey(); + Integer keyId = getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); - entity.setTenantId(fromTimeUUID(tenantId.getId())); - entity.setEntityId(fromTimeUUID(entityId.getId())); + entity.setTenantId(tenantId.getId()); + entity.setEntityId(entityId.getId()); entity.setTs(tsKvEntry.getTs()); - entity.setKey(tsKvEntry.getKey()); + entity.setKey(keyId); entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return queue.add(entity); + log.trace("Saving entity to timescale db: {}", entity); + return queue.add(new EntityContainer(entity, null)); } @Override @@ -175,16 +164,18 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Override public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - return Futures.immediateFuture(null); + return getSaveLatestFuture(entityId, tsKvEntry); } @Override public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + String strKey = query.getKey(); + Integer keyId = getOrSaveKeyId(strKey); return service.submit(() -> { tsKvRepository.delete( - fromTimeUUID(tenantId.getId()), - fromTimeUUID(entityId.getId()), - query.getKey(), + tenantId.getId(), + entityId.getId(), + keyId, query.getStartTs(), query.getEndTs()); return null; @@ -193,7 +184,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Override public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> null); + return getRemoveLatestFuture(tenantId, entityId, query); } @Override @@ -201,37 +192,60 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements return service.submit(() -> null); } - private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); - return Futures.transformAsync(future, entryList -> { - if (entryList.size() == 1) { - return save(tenantId, entityId, entryList.get(0), 0L); - } else { - log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey()); - } - return Futures.immediateFuture(null); - }, service); - } - - private ListenableFuture> findLatestByQuery(TenantId tenantId, EntityId entityId, TsKvQuery query) { - return getLatest(tenantId, entityId, query.getKey(), query.getStartTs(), query.getEndTs()); + private ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + String strKey = query.getKey(); + Integer keyId = getOrSaveKeyId(strKey); + List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( + tenantId.getId(), + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), TS))); + timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey)); + return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities)); } - private ListenableFuture> getLatest(TenantId tenantId, EntityId entityId, String key, long start, long end) { - return Futures.immediateFuture(tsKvRepository.findAllWithLimit( - fromTimeUUID(tenantId.getId()), - fromTimeUUID(entityId.getId()), - key, - start, - end, - new PageRequest(0, 1, - new Sort(Sort.Direction.DESC, TS)))); + private Integer getOrSaveKeyId(String strKey) { + Integer keyId = tsKvDictionaryMap.get(strKey); + if (keyId == null) { + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + tsCreationLock.lock(); + try { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + TsKvDictionary tsKvDictionary = new TsKvDictionary(); + tsKvDictionary.setKey(strKey); + try { + TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); + tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (ConstraintViolationException e) { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); + tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + } + } finally { + tsCreationLock.unlock(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + tsKvDictionaryMap.put(strKey, keyId); + } + } + return keyId; } private ListenableFuture>> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { - String entityIdStr = fromTimeUUID(entityId.getId()); - String tenantIdStr = fromTimeUUID(tenantId.getId()); - CompletableFuture> listCompletableFuture = switchAgregation(key, startTs, endTs, timeBucket, aggregation, entityIdStr, tenantIdStr); + CompletableFuture> listCompletableFuture = switchAgregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); SettableFuture> listenableFuture = SettableFuture.create(); listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { if (throwable != null) { @@ -245,9 +259,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements List> result = new ArrayList<>(); timescaleTsKvEntities.forEach(entity -> { if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityIdStr); - entity.setTenantId(tenantIdStr); - entity.setKey(key); + entity.setEntityId(entityId.getId()); + entity.setTenantId(tenantId.getId()); + entity.setStrKey(key); result.add(Optional.of(DaoUtil.getData(entity))); } else { result.add(Optional.empty()); @@ -260,69 +274,74 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements }); } - private CompletableFuture> switchAgregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, String entityIdStr, String tenantIdStr) { + private CompletableFuture> switchAgregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) { switch (aggregation) { case AVG: - return findAvg(key, startTs, endTs, timeBucket, entityIdStr, tenantIdStr); + return findAvg(key, startTs, endTs, timeBucket, entityId, tenantId); case MAX: - return findMax(key, startTs, endTs, timeBucket, entityIdStr, tenantIdStr); + return findMax(key, startTs, endTs, timeBucket, entityId, tenantId); case MIN: - return findMin(key, startTs, endTs, timeBucket, entityIdStr, tenantIdStr); + return findMin(key, startTs, endTs, timeBucket, entityId, tenantId); case SUM: - return findSum(key, startTs, endTs, timeBucket, entityIdStr, tenantIdStr); + return findSum(key, startTs, endTs, timeBucket, entityId, tenantId); case COUNT: - return findCount(key, startTs, endTs, timeBucket, entityIdStr, tenantIdStr); + return findCount(key, startTs, endTs, timeBucket, entityId, tenantId); default: throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, String entityIdStr, String tenantIdStr) { + private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findAvg( - tenantIdStr, - entityIdStr, - key, + tenantId, + entityId, + keyId, timeBucket, startTs, endTs); } - private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, String entityIdStr, String tenantIdStr) { + private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findMax( - tenantIdStr, - entityIdStr, - key, + tenantId, + entityId, + keyId, timeBucket, startTs, endTs); } - private CompletableFuture> findMin(String key, long startTs, long endTs, long timeBucket, String entityIdStr, String tenantIdStr) { + private CompletableFuture> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findMin( - tenantIdStr, - entityIdStr, - key, + tenantId, + entityId, + keyId, timeBucket, startTs, endTs); } - private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, String entityIdStr, String tenantIdStr) { + private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findSum( - tenantIdStr, - entityIdStr, - key, + tenantId, + entityId, + keyId, timeBucket, startTs, endTs); } - private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, String entityIdStr, String tenantIdStr) { + private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findCount( - tenantIdStr, - entityIdStr, - key, + tenantId, + entityId, + keyId, timeBucket, startTs, endTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index 9f68bec921..a4b15abd26 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; import org.thingsboard.server.dao.util.TimescaleDBTsDao; import java.util.List; +import java.util.UUID; @TimescaleDBTsDao public interface TsKvTimescaleRepository extends CrudRepository { @@ -35,31 +36,21 @@ public interface TsKvTimescaleRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") List findAllWithLimit( - @Param("tenantId") String tenantId, - @Param("entityId") String entityId, - @Param("entityKey") String key, + @Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityKey") int key, @Param("startTs") long startTs, @Param("endTs") long endTs, Pageable pageable); - @Query(value = "SELECT tskv.tenant_id as tenant_id, tskv.entity_id as entity_id, tskv.key as key, last(tskv.ts,tskv.ts) as ts," + - " last(tskv.bool_v, tskv.ts) as bool_v, last(tskv.str_v, tskv.ts) as str_v," + - " last(tskv.long_v, tskv.ts) as long_v, last(tskv.dbl_v, tskv.ts) as dbl_v" + - " FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS varchar) " + - "AND tskv.entity_id = cast(:entityId AS varchar) " + - "GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key", nativeQuery = true) - List findAllLatestValues( - @Param("tenantId") String tenantId, - @Param("entityId") String entityId); - @Transactional @Modifying @Query("DELETE FROM TimescaleTsKvEntity tskv WHERE tskv.tenantId = :tenantId " + "AND tskv.entityId = :entityId " + "AND tskv.key = :entityKey " + "AND tskv.ts > :startTs AND tskv.ts <= :endTs") - void delete(@Param("tenantId") String tenantId, - @Param("entityId") String entityId, - @Param("entityKey") String key, + void delete(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("entityKey") int key, @Param("startTs") long startTs, @Param("endTs") long endTs); 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 deleted file mode 100644 index b8b4766e70..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlLatestInsertRepository.java +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Copyright © 2016-2020 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.jdbc.core.BatchPreparedStatementSetter; -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; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Types; -import java.util.List; - -@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); - - 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); - } - - @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 - 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 deleted file mode 100644 index e909937c6b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/HsqlTimeseriesInsertRepository.java +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright © 2016-2020 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.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; -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 -@HsqlDao -@Repository -@Transactional -public class HsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository { - - 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 = 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); - - 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); - } - - @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 - protected void saveOrUpdateBoolean(TsKvEntity 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(TsKvEntity 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(TsKvEntity 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(TsKvEntity 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/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java deleted file mode 100644 index dfee164246..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java +++ /dev/null @@ -1,436 +0,0 @@ -/** - * Copyright © 2016-2020 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 com.google.common.collect.Lists; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -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; -import org.thingsboard.server.common.data.UUIDConverter; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.Aggregation; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; -import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; -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; -import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; -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; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; - -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; - - -@Component -@Slf4j -@SqlTsDao -public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements TimeseriesDao { - - @Autowired - private TsKvRepository tsKvRepository; - - @Autowired - private TsKvLatestRepository tsKvLatestRepository; - - @Autowired - private AbstractTimeseriesInsertRepository insertRepository; - - @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); - } - - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(entityId, query); - } else { - long stepTs = query.getStartTs(); - List>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + query.getInterval(); - long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); - stepTs = endTs; - } - return getTskvEntriesFuture(Futures.allAsList(futures)); - } - } - - private ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - List> entitiesFutures = new ArrayList<>(); - String entityIdStr = fromTimeUUID(entityId.getId()); - switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures, entityIdStr); - - SettableFuture listenableFuture = SettableFuture.create(); - - CompletableFuture> entities = - CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) - .thenApply(v -> entitiesFutures.stream() - .map(CompletableFuture::join) - .collect(Collectors.toList())); - - entities.whenComplete((tsKvEntities, throwable) -> { - if (throwable != null) { - listenableFuture.setException(throwable); - } else { - TsKvEntity result = null; - for (TsKvEntity entity : tsKvEntities) { - if (entity.isNotEmpty()) { - result = entity; - break; - } - } - listenableFuture.set(result); - } - }); - return Futures.transform(listenableFuture, entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityIdStr); - entity.setEntityType(entityId.getEntityType()); - entity.setKey(key); - entity.setTs(ts); - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); - } - }); - } - - private void switchAgregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures, String entityIdStr) { - switch (aggregation) { - case AVG: - findAvg(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); - break; - case MAX: - findMax(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); - break; - case MIN: - findMin(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); - break; - case SUM: - findSum(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); - break; - case COUNT: - findCount(entityId, key, startTs, endTs, entitiesFutures, entityIdStr); - break; - default: - throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); - } - } - - private void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures, String entityIdStr) { - entitiesFutures.add(tsKvRepository.findCount( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - } - - private void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures, String entityIdStr) { - entitiesFutures.add(tsKvRepository.findSum( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - } - - private void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures, String entityIdStr) { - entitiesFutures.add(tsKvRepository.findStringMin( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMin( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - } - - private void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures, String entityIdStr) { - entitiesFutures.add(tsKvRepository.findStringMax( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMax( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - } - - private void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures, String entityIdStr) { - entitiesFutures.add(tsKvRepository.findAvg( - entityIdStr, - entityId.getEntityType(), - key, - startTs, - endTs)); - } - - private ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { - return Futures.immediateFuture( - DaoUtil.convertDataList( - tsKvRepository.findAllWithLimit( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - query.getKey(), - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), "ts"))))); - } - - @Override - public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { - TsKvLatestCompositeKey compositeKey = - new TsKvLatestCompositeKey( - entityId.getEntityType(), - fromTimeUUID(entityId.getId()), - key); - Optional entry = tsKvLatestRepository.findById(compositeKey); - TsKvEntry result; - if (entry.isPresent()) { - result = DaoUtil.getData(entry.get()); - } else { - result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); - } - return Futures.immediateFuture(result); - } - - @Override - public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { - return Futures.immediateFuture( - DaoUtil.convertDataList(Lists.newArrayList( - tsKvLatestRepository.findAllByEntityTypeAndEntityId( - entityId.getEntityType(), - UUIDConverter.fromTimeUUID(entityId.getId()))))); - } - - @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { - TsKvEntity entity = new TsKvEntity(); - entity.setEntityType(entityId.getEntityType()); - entity.setEntityId(fromTimeUUID(entityId.getId())); - entity.setTs(tsKvEntry.getTs()); - entity.setKey(tsKvEntry.getKey()); - entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); - entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); - entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); - entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - log.trace("Saving entity: {}", entity); - return tsQueue.add(entity); - } - - @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return Futures.immediateFuture(null); - } - - @Override - public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - TsKvLatestEntity latestEntity = new TsKvLatestEntity(); - latestEntity.setEntityType(entityId.getEntityType()); - latestEntity.setEntityId(fromTimeUUID(entityId.getId())); - latestEntity.setTs(tsKvEntry.getTs()); - latestEntity.setKey(tsKvEntry.getKey()); - latestEntity.setStrValue(tsKvEntry.getStrValue().orElse(null)); - latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); - latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); - latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); - return tsLatestQueue.add(latestEntity); - } - - @Override - public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> { - tsKvRepository.delete( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - query.getKey(), - query.getStartTs(), - query.getEndTs()); - return null; - }); - } - - @Override - public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - ListenableFuture latestFuture = findLatest(tenantId, entityId, query.getKey()); - - ListenableFuture booleanFuture = Futures.transform(latestFuture, tsKvEntry -> { - long ts = tsKvEntry.getTs(); - return ts > query.getStartTs() && ts <= query.getEndTs(); - }, service); - - ListenableFuture removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { - if (isRemove) { - TsKvLatestEntity latestEntity = new TsKvLatestEntity(); - latestEntity.setEntityType(entityId.getEntityType()); - latestEntity.setEntityId(fromTimeUUID(entityId.getId())); - latestEntity.setKey(query.getKey()); - return service.submit(() -> { - tsKvLatestRepository.delete(latestEntity); - return null; - }); - } - return Futures.immediateFuture(null); - }, service); - - final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>(); - Futures.addCallback(removedLatestFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable Void result) { - if (query.getRewriteLatestIfDeleted()) { - ListenableFuture savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { - if (isRemove) { - return getNewLatestEntryFuture(tenantId, entityId, query); - } - return Futures.immediateFuture(null); - }, service); - - try { - resultFuture.set(savedLatestFuture.get()); - } catch (InterruptedException | ExecutionException e) { - log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e); - } - } else { - resultFuture.set(null); - } - } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] Failed to process remove of the latest value", entityId, t); - } - }); - return resultFuture; - } - - private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); - return Futures.transformAsync(future, entryList -> { - if (entryList.size() == 1) { - return saveLatest(tenantId, entityId, entryList.get(0)); - } else { - log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey()); - } - return Futures.immediateFuture(null); - }, service); - } - - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> null); - } -} \ 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 deleted file mode 100644 index d35f17b7c1..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright © 2016-2020 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.jdbc.core.BatchPreparedStatementSetter; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; -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 -@Transactional -public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepository { - - private static final String TS_KV_CONSTRAINT = "(entity_type, entity_id, key, ts)"; - - 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); - - 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); - } - - @Override - protected void saveOrUpdateBoolean(TsKvEntity 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(TsKvEntity 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", replaceNullChars(entity.getStrValue())) - .executeUpdate(); - } - - @Override - protected void saveOrUpdateLong(TsKvEntity 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(TsKvEntity 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(); - } - - @Override - public void saveOrUpdate(List entities) { - jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { - @Override - public void setValues(PreparedStatement ps, int i) throws SQLException { - TsKvEntity tsKvEntity = entities.get(i); - ps.setString(1, tsKvEntity.getEntityType().name()); - ps.setString(2, tsKvEntity.getEntityId()); - ps.setString(3, tsKvEntity.getKey()); - ps.setLong(4, tsKvEntity.getTs()); - - if (tsKvEntity.getBooleanValue() != null) { - ps.setBoolean(5, tsKvEntity.getBooleanValue()); - ps.setBoolean(9, tsKvEntity.getBooleanValue()); - } else { - ps.setNull(5, Types.BOOLEAN); - ps.setNull(9, Types.BOOLEAN); - } - - ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); - ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); - - - if (tsKvEntity.getLongValue() != null) { - ps.setLong(7, tsKvEntity.getLongValue()); - ps.setLong(11, tsKvEntity.getLongValue()); - } else { - ps.setNull(7, Types.BIGINT); - ps.setNull(11, Types.BIGINT); - } - - if (tsKvEntity.getDoubleValue() != null) { - ps.setDouble(8, tsKvEntity.getDoubleValue()); - ps.setDouble(12, tsKvEntity.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 diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 5dc2e7aef2..d0ebb49572 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -97,7 +97,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem @Value("${cassandra.query.set_null_values_enabled}") private boolean setNullValuesEnabled; - private TsPartitionDate tsFormat; + private NoSqlTsPartitionDate tsFormat; private PreparedStatement partitionInsertStmt; private PreparedStatement partitionInsertTtlStmt; @@ -120,7 +120,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem super.startExecutor(); if (!isInstall()) { getFetchStmt(Aggregation.NONE, DESC_ORDER); - Optional partition = TsPartitionDate.parse(partitioning); + Optional partition = NoSqlTsPartitionDate.parse(partitioning); if (partition.isPresent()) { tsFormat = partition.get(); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsPartitionDate.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/NoSqlTsPartitionDate.java similarity index 87% rename from dao/src/main/java/org/thingsboard/server/dao/timeseries/TsPartitionDate.java rename to dao/src/main/java/org/thingsboard/server/dao/timeseries/NoSqlTsPartitionDate.java index 351a94d3cf..0ce26baae6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsPartitionDate.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/NoSqlTsPartitionDate.java @@ -21,7 +21,7 @@ import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalUnit; import java.util.Optional; -public enum TsPartitionDate { +public enum NoSqlTsPartitionDate { MINUTES("yyyy-MM-dd-HH-mm", ChronoUnit.MINUTES), HOURS("yyyy-MM-dd-HH", ChronoUnit.HOURS), DAYS("yyyy-MM-dd", ChronoUnit.DAYS), MONTHS("yyyy-MM", ChronoUnit.MONTHS), YEARS("yyyy", ChronoUnit.YEARS),INDEFINITE("",ChronoUnit.FOREVER); @@ -29,7 +29,7 @@ public enum TsPartitionDate { private final transient TemporalUnit truncateUnit; public final static LocalDateTime EPOCH_START = LocalDateTime.ofEpochSecond(0,0, ZoneOffset.UTC); - TsPartitionDate(String pattern, TemporalUnit truncateUnit) { + NoSqlTsPartitionDate(String pattern, TemporalUnit truncateUnit) { this.pattern = pattern; this.truncateUnit = truncateUnit; } @@ -56,10 +56,10 @@ public enum TsPartitionDate { } } - public static Optional parse(String name) { - TsPartitionDate partition = null; + public static Optional parse(String name) { + NoSqlTsPartitionDate partition = null; if (name != null) { - for (TsPartitionDate partitionDate : TsPartitionDate.values()) { + for (NoSqlTsPartitionDate partitionDate : NoSqlTsPartitionDate.values()) { if (partitionDate.name().equalsIgnoreCase(name)) { partition = partitionDate; break; diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/PsqlPartition.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/PsqlPartition.java new file mode 100644 index 0000000000..5600ec6785 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/PsqlPartition.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 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.timeseries; + +import lombok.Data; + +import java.text.SimpleDateFormat; +import java.util.Date; + +@Data +public class PsqlPartition { + + private static final String TABLE_REGEX = "ts_kv_"; + + private long start; + private long end; + private String partitionDate; + private String query; + + public PsqlPartition(long start, long end, String pattern) { + this.start = start; + this.end = end; + this.partitionDate = new SimpleDateFormat(pattern).format(new Date(start)); + this.query = createStatement(start, end, partitionDate); + } + + private String createStatement(long start, long end, String partitionDate) { + return "CREATE TABLE IF NOT EXISTS " + TABLE_REGEX + partitionDate + " PARTITION OF ts_kv(PRIMARY KEY (entity_id, key, ts)) FOR VALUES FROM (" + start + ") TO (" + end + ")"; + } +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlTsPartitionDate.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlTsPartitionDate.java new file mode 100644 index 0000000000..202a6d9305 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/SqlTsPartitionDate.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2020 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.timeseries; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalUnit; +import java.util.Optional; + +public enum SqlTsPartitionDate { + + MINUTES("yyyy_MM_dd_HH_mm", ChronoUnit.MINUTES), HOURS("yyyy_MM_dd_HH", ChronoUnit.HOURS), DAYS("yyyy_MM_dd", ChronoUnit.DAYS), MONTHS("yyyy_MM", ChronoUnit.MONTHS), YEARS("yyyy", ChronoUnit.YEARS), INDEFINITE("indefinite", ChronoUnit.FOREVER); + + private final String pattern; + private final transient TemporalUnit truncateUnit; + public final static LocalDateTime EPOCH_START = LocalDateTime.ofEpochSecond(0, 0, ZoneOffset.UTC); + + SqlTsPartitionDate(String pattern, TemporalUnit truncateUnit) { + this.pattern = pattern; + this.truncateUnit = truncateUnit; + } + + public String getPattern() { + return pattern; + } + + public TemporalUnit getTruncateUnit() { + return truncateUnit; + } + + public LocalDateTime trancateTo(LocalDateTime time) { + switch (this) { + case MINUTES: + return time.truncatedTo(ChronoUnit.MINUTES); + case HOURS: + return time.truncatedTo(ChronoUnit.HOURS); + case DAYS: + return time.truncatedTo(ChronoUnit.DAYS); + case MONTHS: + return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1); + case YEARS: + return time.truncatedTo(ChronoUnit.DAYS).withDayOfYear(1); + case INDEFINITE: + return EPOCH_START; + default: + throw new RuntimeException("Failed to parse partitioning property!"); + } + } + + public LocalDateTime plusTo(LocalDateTime time) { + switch (this) { + case MINUTES: + return time.plusMinutes(1); + case HOURS: + return time.plusHours(1); + case DAYS: + return time.plusDays(1); + case MONTHS: + return time.plusMonths(1); + case YEARS: + return time.plusYears(1); + default: + throw new RuntimeException("Failed to parse partitioning property!"); + } + } + + public static Optional parse(String name) { + SqlTsPartitionDate partition = null; + if (name != null) { + for (SqlTsPartitionDate partitionDate : SqlTsPartitionDate.values()) { + if (partitionDate.name().equalsIgnoreCase(name)) { + partition = partitionDate; + break; + } + } + } + return Optional.of(partition); + } +} \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index 0407ba78af..bcdc436608 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -17,7 +17,25 @@ CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; CREATE TABLE IF NOT EXISTS tenant_ts_kv ( - tenant_id varchar(31) NOT NULL, + tenant_id uuid NOT NULL, + entity_id uuid NOT NULL, + key int NOT NULL, + ts bigint NOT NULL, + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision, + CONSTRAINT ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) +); + +CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) +); + +CREATE TABLE IF NOT EXISTS ts_kv_latest ( + entity_type varchar(255) NOT NULL, entity_id varchar(31) NOT NULL, key varchar(255) NOT NULL, ts bigint NOT NULL, @@ -25,7 +43,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv ( str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) ); SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-ts.sql b/dao/src/main/resources/sql/schema-ts-hsql.sql similarity index 100% rename from dao/src/main/resources/sql/schema-ts.sql rename to dao/src/main/resources/sql/schema-ts-hsql.sql diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql new file mode 100644 index 0000000000..bd9e3a693d --- /dev/null +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -0,0 +1,43 @@ +-- +-- Copyright © 2016-2020 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. +-- + +CREATE TABLE IF NOT EXISTS ts_kv ( + entity_id uuid NOT NULL, + key int NOT NULL, + ts bigint NOT NULL, + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision +) PARTITION BY RANGE (ts); + +CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) +); + +CREATE TABLE IF NOT EXISTS ts_kv_latest ( + entity_type varchar(255) NOT NULL, + entity_id varchar(31) NOT NULL, + key varchar(255) NOT NULL, + ts bigint NOT NULL, + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision, + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) +); \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java index f5916c87f9..5ceee6c55d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java @@ -30,7 +30,7 @@ import org.springframework.test.context.support.DirtiesContextTestExecutionListe * Created by Valerii Sosliuk on 4/22/2017. */ @RunWith(SpringRunner.class) -@ContextConfiguration(classes = {JpaDaoConfig.class, SqlTsDaoConfig.class, JpaDbunitTestConfig.class}) +@ContextConfiguration(classes = {JpaDaoConfig.class, HsqlTsDaoConfig.class, JpaDbunitTestConfig.class}) @TestPropertySource("classpath:sql-test.properties") @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, diff --git a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java index ef8602d3fa..f4d11b328e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java @@ -30,9 +30,23 @@ public class JpaDaoTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties" ); +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-ts-psql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), +// "sql/drop-all-tables.sql", +// "sql-test.properties" +// ); + +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-timescale.sql", "sql/schema-timescale-idx.sql", "sql/schema-entities.sql", "sql/system-data.sql"), +// "sql/timescale/drop-all-tables.sql", +// "sql-test.properties" +// ); + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index fb36e08290..caddbabc35 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -30,9 +30,23 @@ public class SqlDaoServiceTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), "sql/drop-all-tables.sql", "sql-test.properties" ); +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-ts-psql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), +// "sql/drop-all-tables.sql", +// "sql-test.properties" +// ); + +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-timescale.sql", "sql/schema-timescale-idx.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), +// "sql/timescale/drop-all-tables.sql", +// "sql-test.properties" +// ); + } diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index a2fd6bb17b..765e0da3d6 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -2,7 +2,8 @@ database.ts.type=sql database.entities.type=sql sql.ts_inserts_executor_type=fixed -sql.ts_inserts_fixed_thread_pool_size=10 +sql.ts_inserts_fixed_thread_pool_size=200 +sql.ts_key_value_partitioning=MONTHS spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.show-sql=false @@ -13,4 +14,23 @@ spring.datasource.username=sa spring.datasource.password= spring.datasource.url=jdbc:hsqldb:file:/tmp/testDb;sql.enforce_size=false spring.datasource.driverClassName=org.hsqldb.jdbc.JDBCDriver -spring.datasource.hikari.maximumPoolSize = 50 \ No newline at end of file +spring.datasource.hikari.maximumPoolSize = 50 + +#database.ts.type=timescale +#database.ts.type=sql +#database.entities.type=sql +# +#sql.ts_inserts_executor_type=fixed +#sql.ts_inserts_fixed_thread_pool_size=200 +#sql.ts_key_value_partitioning=MONTHS +# +#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true +#spring.jpa.show-sql=false +#spring.jpa.hibernate.ddl-auto=none +#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect +# +#spring.datasource.username=postgres +#spring.datasource.password=postgres +#spring.datasource.url=jdbc:postgresql://localhost:5432/sqltest +#spring.datasource.driverClassName=org.postgresql.Driver +#spring.datasource.hikari.maximumPoolSize = 50 \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index e50d307c8e..08d018dc1b 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -13,6 +13,7 @@ DROP TABLE IF EXISTS relation; DROP TABLE IF EXISTS tb_user; DROP TABLE IF EXISTS tenant; DROP TABLE IF EXISTS tenant_ts_kv; +DROP TABLE IF EXISTS ts_kv_latest; DROP TABLE IF EXISTS user_credentials; DROP TABLE IF EXISTS widget_type; DROP TABLE IF EXISTS widgets_bundle; diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index ce420481b7..ae615ef636 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -19,7 +19,7 @@ version: '2.2' services: postgres: restart: always - image: "postgres:9.6" + image: "postgres:10" ports: - "5432" environment: diff --git a/k8s/postgres.yml b/k8s/postgres.yml index 9ee1d8a72a..56679ff880 100644 --- a/k8s/postgres.yml +++ b/k8s/postgres.yml @@ -51,7 +51,7 @@ spec: containers: - name: postgres imagePullPolicy: Always - image: postgres:9.6 + image: postgres:10 ports: - containerPort: 5432 name: postgres diff --git a/msa/tb/docker-postgres/start-db.sh b/msa/tb/docker-postgres/start-db.sh index b0622a63df..e7b873fe83 100644 --- a/msa/tb/docker-postgres/start-db.sh +++ b/msa/tb/docker-postgres/start-db.sh @@ -20,10 +20,10 @@ firstlaunch=${DATA_FOLDER}/.firstlaunch if [ ! -d ${PGDATA} ]; then mkdir -p ${PGDATA} chown -R postgres:postgres ${PGDATA} - su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl initdb -U postgres' + su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl initdb -U postgres' fi -su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl -l /var/log/postgres/postgres.log -w start' +su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl -l /var/log/postgres/postgres.log -w start' if [ ! -f ${firstlaunch} ]; then su postgres -c 'psql -U postgres -d postgres -c "CREATE DATABASE thingsboard"' diff --git a/msa/tb/docker-postgres/stop-db.sh b/msa/tb/docker-postgres/stop-db.sh index 5f400cafff..fc5cb1784c 100644 --- a/msa/tb/docker-postgres/stop-db.sh +++ b/msa/tb/docker-postgres/stop-db.sh @@ -15,4 +15,4 @@ # limitations under the License. # -su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl stop' +su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl stop' From 23919b3d5f62e1eff623ffbe127ed42317c2cdea Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Tue, 28 Jan 2020 01:17:22 +0200 Subject: [PATCH 177/261] Add Latvian 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 | 3 +- 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-lv_LV.json | 1697 ++++++++++++++++++ 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 +- 11 files changed, 1717 insertions(+), 10 deletions(-) create mode 100644 ui/src/app/locale/locale.constant-lv_LV.json diff --git a/ui/src/app/locale/locale.constant-cs_CZ.json b/ui/src/app/locale/locale.constant-cs_CZ.json index 848dd89b7c..8e8a6b8a2f 100644 --- a/ui/src/app/locale/locale.constant-cs_CZ.json +++ b/ui/src/app/locale/locale.constant-cs_CZ.json @@ -1652,7 +1652,8 @@ "fa_IR": "Persian", "uk_UA": "Ukrainian", "cs_CZ": "Česky", - "el_GR": "Řečtina" + "el_GR": "Řečtina", + "lv_LV": "Lotyština" } } } diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index d313770b89..d5f43f4ef4 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -1700,7 +1700,8 @@ "fa_IR": "Persisch", "uk_UA": "Ukrainisch", "cs_CZ": "Tschechisch", - "el_GR": "Griechisch" + "el_GR": "Griechisch", + "lv_LV": "Lettisch" } } } diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index f3051e98c1..9b9783a9ad 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -2616,7 +2616,8 @@ "fa_IR": "Περσικά", "uk_UA": "Ουκρανικά", "cs_CZ": "Τσέχικα", - "el_GR": "Ελληνικά" + "el_GR": "Ελληνικά", + "lv_LV": "Λετονικά" } } } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index dde68c35b4..37caf9875b 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1806,7 +1806,8 @@ "fa_IR": "Persian", "uk_UA": "Ukrainian", "cs_CZ": "Czech", - "el_GR": "Greek" + "el_GR": "Greek", + "lv_LV": "Latvian" } } } diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 5cebe22ef6..75abc889b3 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -1774,7 +1774,8 @@ "fa_IR": "Persa", "uk_UA": "Ucraniano", "cs_CZ": "Checo", - "el_GR": "Griego" + "el_GR": "Griego", + "lv_LV": "Letón" } } } diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index 5c922bfd49..f234afbd88 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -1184,7 +1184,8 @@ "fa_IR": "Persane", "uk_UA": "Ukrainien", "cs_CZ": "Tchèque", - "el_GR": "Grec" + "el_GR": "Grec", + "lv_LV": "Letton" } }, "layout": { diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index cf7529614b..1c65d04096 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -1715,7 +1715,8 @@ "fa_IR": "Persiana", "uk_UA": "Ucraino", "cs_CZ": "Ceco", - "el_GR": "Greco" + "el_GR": "Greco", + "lv_LV": "lettone" } } } diff --git a/ui/src/app/locale/locale.constant-lv_LV.json b/ui/src/app/locale/locale.constant-lv_LV.json new file mode 100644 index 0000000000..4ddbb1856f --- /dev/null +++ b/ui/src/app/locale/locale.constant-lv_LV.json @@ -0,0 +1,1697 @@ +{ + "access": { + "unauthorized": "Unauthorized", + "unauthorized-access": "Unauthorized Access", + "unauthorized-access-text": "You should sign in to have access to this resource!", + "access-forbidden": "Access Forbidden", + "access-forbidden-text": "You haven't access rights to this location!
Try to sign in with different user if you still wish to gain access to this location.", + "refresh-token-expired": "Session has expired", + "refresh-token-failed": "Unable to refresh session" + }, + "action": { + "activate": "Activate", + "suspend": "Suspend", + "save": "Save", + "saveAs": "Save as", + "cancel": "Cancel", + "ok": "OK", + "delete": "Delete", + "add": "Add", + "yes": "Yes", + "no": "No", + "update": "Update", + "remove": "Remove", + "search": "Search", + "clear-search": "Clear search", + "assign": "Assign", + "unassign": "Unassign", + "share": "Share", + "make-private": "Make private", + "apply": "Apply", + "apply-changes": "Apply changes", + "edit-mode": "Edit mode", + "enter-edit-mode": "Enter edit mode", + "decline-changes": "Decline changes", + "close": "Close", + "back": "Back", + "run": "Run", + "sign-in": "Sign in!", + "edit": "Edit", + "view": "View", + "create": "Create", + "drag": "Drag", + "refresh": "Refresh", + "undo": "Undo", + "copy": "Copy", + "paste": "Paste", + "copy-reference": "Copy reference", + "paste-reference": "Paste reference", + "import": "Import", + "export": "Export", + "share-via": "Share via {{provider}}", + "continue": "Continue" + }, + "aggregation": { + "aggregation": "Aggregation", + "function": "Data aggregation function", + "limit": "Max values", + "group-interval": "Grouping interval", + "min": "Min", + "max": "Max", + "avg": "Average", + "sum": "Sum", + "count": "Count", + "none": "None" + }, + "admin": { + "general": "General", + "general-settings": "General Settings", + "outgoing-mail": "Mail Server", + "outgoing-mail-settings": "Outgoing Mail Server Settings", + "system-settings": "System Settings", + "test-mail-sent": "Test mail was successfully sent!", + "base-url": "Base URL", + "base-url-required": "Base URL is required.", + "mail-from": "Mail From", + "mail-from-required": "Mail From is required.", + "smtp-protocol": "SMTP 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-invalid": "That doesn't look like a valid smtp port.", + "timeout-msec": "Timeout (msec)", + "timeout-required": "Timeout is required.", + "timeout-invalid": "That doesn't look like a valid timeout.", + "enable-tls": "Enable TLS", + "send-test-mail": "Send test mail" + }, + "alarm": { + "alarm": "Alarm", + "alarms": "Alarms", + "select-alarm": "Select alarm", + "no-alarms-matching": "No alarms matching '{{entity}}' were found.", + "alarm-required": "Alarm is required", + "alarm-status": "Alarm status", + "search-status": { + "ANY": "Any", + "ACTIVE": "Active", + "CLEARED": "Cleared", + "ACK": "Acknowledged", + "UNACK": "Unacknowledged" + }, + "display-status": { + "ACTIVE_UNACK": "Active Unacknowledged", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "Cleared Unacknowledged", + "CLEARED_ACK": "Cleared Acknowledged" + }, + "no-alarms-prompt": "No alarms found", + "created-time": "Created time", + "type": "Type", + "severity": "Severity", + "originator": "Originator", + "originator-type": "Originator type", + "details": "Details", + "status": "Status", + "alarm-details": "Alarm details", + "start-time": "Start time", + "end-time": "End time", + "ack-time": "Acknowledged time", + "clear-time": "Cleared time", + "severity-critical": "Critical", + "severity-major": "Major", + "severity-minor": "Minor", + "severity-warning": "Warning", + "severity-indeterminate": "Indeterminate", + "acknowledge": "Acknowledge", + "clear": "Clear", + "search": "Search alarms", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selected", + "no-data": "No data to display", + "polling-interval": "Alarms polling interval (sec)", + "polling-interval-required": "Alarms polling interval is required.", + "min-polling-interval-message": "At least 1 sec polling interval is allowed.", + "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Acknowledge Alarm", + "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", + "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Clear Alarm", + "clear-alarm-text": "Are you sure you want to clear Alarm?", + "alarm-status-filter": "Alarm Status Filter" + }, + "alias": { + "add": "Add alias", + "edit": "Edit alias", + "name": "Alias name", + "name-required": "Alias name is required", + "duplicate-alias": "Alias with same name is already exists.", + "filter-type-single-entity": "Single entity", + "filter-type-entity-list": "Entity list", + "filter-type-entity-name": "Entity name", + "filter-type-state-entity": "Entity from dashboard state", + "filter-type-state-entity-description": "Entity taken from dashboard state parameters", + "filter-type-asset-type": "Asset type", + "filter-type-asset-type-description": "Assets of type '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'", + "filter-type-device-type": "Device type", + "filter-type-device-type-description": "Devices of type '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", + "filter-type-entity-view-type": "Entity View type", + "filter-type-entity-view-type-description": "Entity Views of type '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Entity Views of type '{{entityView}}' and with name starting with '{{prefix}}'", + "filter-type-relations-query": "Relations query", + "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Asset search query", + "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Device search query", + "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Entity view search query", + "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "entity-filter": "Entity filter", + "resolve-multiple": "Resolve as multiple entities", + "filter-type": "Filter type", + "filter-type-required": "Filter type is required.", + "entity-filter-no-entity-matched": "No entities matching specified filter were found.", + "no-entity-filter-specified": "No entity filter specified", + "root-state-entity": "Use dashboard state entity as root", + "root-entity": "Root entity", + "state-entity-parameter-name": "State entity parameter name", + "default-state-entity": "Default state entity", + "default-entity-parameter-name": "By default", + "max-relation-level": "Max relation level", + "unlimited-level": "Unlimited level", + "state-entity": "Dashboard state entity", + "all-entities": "All entities", + "any-relation": "any" + }, + "asset": { + "asset": "Asset", + "assets": "Assets", + "management": "Asset management", + "view-assets": "View Assets", + "add": "Add Asset", + "assign-to-customer": "Assign to customer", + "assign-asset-to-customer": "Assign Asset(s) To Customer", + "assign-asset-to-customer-text": "Please select the assets to assign to the customer", + "no-assets-text": "No assets found", + "assign-to-customer-text": "Please select the customer to assign the asset(s)", + "public": "Public", + "assignedToCustomer": "Assigned to customer", + "make-public": "Make asset public", + "make-private": "Make asset private", + "unassign-from-customer": "Unassign from customer", + "delete": "Delete asset", + "asset-public": "Asset is public", + "asset-type": "Asset type", + "asset-type-required": "Asset type is required.", + "select-asset-type": "Select asset type", + "enter-asset-type": "Enter asset type", + "any-asset": "Any asset", + "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", + "asset-type-list-empty": "No asset types selected.", + "asset-types": "Asset types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "type": "Type", + "type-required": "Type is required.", + "details": "Details", + "events": "Events", + "add-asset-text": "Add new asset", + "asset-details": "Asset details", + "assign-assets": "Assign assets", + "assign-assets-text": "Assign { count, plural, 1 {1 asset} other {# assets} } to customer", + "delete-assets": "Delete assets", + "unassign-assets": "Unassign assets", + "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer", + "assign-new-asset": "Assign new asset", + "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?", + "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.", + "delete-assets-title": "Are you sure you want to delete { count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "Delete { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.", + "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?", + "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.", + "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?", + "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.", + "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?", + "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.", + "unassign-asset": "Unassign asset", + "unassign-assets-title": "Are you sure you want to unassign { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", + "copyId": "Copy asset Id", + "idCopiedMessage": "Asset Id has been copied to clipboard", + "select-asset": "Select asset", + "no-assets-matching": "No assets matching '{{entity}}' were found.", + "asset-required": "Asset is required", + "name-starts-with": "Asset name starts with", + "import": "Import assets", + "asset-file": "Asset file" + }, + "attribute": { + "attributes": "Attributes", + "latest-telemetry": "Latest telemetry", + "attributes-scope": "Entity attributes scope", + "scope-latest-telemetry": "Latest telemetry", + "scope-client": "Client attributes", + "scope-server": "Server attributes", + "scope-shared": "Shared attributes", + "add": "Add attribute", + "key": "Key", + "last-update-time": "Last update time", + "key-required": "Attribute key is required.", + "value": "Value", + "value-required": "Attribute value is required.", + "delete-attributes-title": "Are you sure you want to delete { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.", + "delete-attributes": "Delete attributes", + "enter-attribute-value": "Enter attribute value", + "show-on-widget": "Show on widget", + "widget-mode": "Widget mode", + "next-widget": "Next widget", + "prev-widget": "Previous widget", + "add-to-dashboard": "Add to dashboard", + "add-widget-to-dashboard": "Add widget to dashboard", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selected", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Audit Logs", + "timestamp": "Timestamp", + "entity-type": "Entity Type", + "entity-name": "Entity Name", + "user": "User", + "type": "Type", + "status": "Status", + "details": "Details", + "type-added": "Added", + "type-deleted": "Deleted", + "type-updated": "Updated", + "type-attributes-updated": "Attributes updated", + "type-attributes-deleted": "Attributes deleted", + "type-rpc-call": "RPC call", + "type-credentials-updated": "Credentials updated", + "type-assigned-to-customer": "Assigned to Customer", + "type-unassigned-from-customer": "Unassigned from Customer", + "type-activated": "Activated", + "type-suspended": "Suspended", + "type-credentials-read": "Credentials read", + "type-attributes-read": "Attributes read", + "type-relation-add-or-update": "Relation updated", + "type-relation-delete": "Relation deleted", + "type-relations-delete": "All relation deleted", + "type-alarm-ack": "Acknowledged", + "type-alarm-clear": "Cleared", + "status-success": "Success", + "status-failure": "Failure", + "audit-log-details": "Audit log details", + "no-audit-logs-prompt": "No logs found", + "action-data": "Action data", + "failure-details": "Failure details", + "search": "Search audit logs", + "clear-search": "Clear search" + }, + "confirm-on-exit": { + "message": "You have unsaved changes. Are you sure you want to leave this page?", + "html-message": "You have unsaved changes.
Are you sure you want to leave this page?", + "title": "Unsaved changes" + }, + "contact": { + "country": "Country", + "city": "City", + "state": "State / Province", + "postal-code": "Zip / Postal Code", + "postal-code-invalid": "Invalid Zip / Postal Code format.", + "address": "Address", + "address2": "Address 2", + "phone": "Phone", + "email": "Email", + "no-address": "No address" + }, + "common": { + "username": "Username", + "password": "Password", + "enter-username": "Enter username", + "enter-password": "Enter password", + "enter-search": "Enter search" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "Customer", + "customers": "Customers", + "management": "Customer management", + "dashboard": "Customer Dashboard", + "dashboards": "Customer Dashboards", + "devices": "Customer Devices", + "entity-views": "Customer Entity Views", + "assets": "Customer Assets", + "public-dashboards": "Public Dashboards", + "public-devices": "Public Devices", + "public-assets": "Public Assets", + "public-entity-views": "Public Entity Views", + "add": "Add Customer", + "delete": "Delete customer", + "manage-customer-users": "Manage customer users", + "manage-customer-devices": "Manage customer devices", + "manage-customer-dashboards": "Manage customer dashboards", + "manage-public-devices": "Manage public devices", + "manage-public-dashboards": "Manage public dashboards", + "manage-customer-assets": "Manage customer assets", + "manage-public-assets": "Manage public assets", + "add-customer-text": "Add new customer", + "no-customers-text": "No customers found", + "customer-details": "Customer details", + "delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?", + "delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.", + "delete-customers-title": "Are you sure you want to delete { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Delete { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.", + "manage-users": "Manage users", + "manage-assets": "Manage assets", + "manage-devices": "Manage devices", + "manage-dashboards": "Manage dashboards", + "title": "Title", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "events": "Events", + "copyId": "Copy customer Id", + "idCopiedMessage": "Customer Id has been copied to clipboard", + "select-customer": "Select customer", + "no-customers-matching": "No customers matching '{{entity}}' were found.", + "customer-required": "Customer is required", + "select-default-customer": "Select default customer", + "default-customer": "Default customer", + "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" + }, + "datetime": { + "date-from": "Date from", + "time-from": "Time from", + "date-to": "Date to", + "time-to": "Time to" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Dashboard management", + "view-dashboards": "View Dashboards", + "add": "Add Dashboard", + "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer", + "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer", + "assign-to-customer-text": "Please select the customer to assign the dashboard(s)", + "assign-to-customer": "Assign to customer", + "unassign-from-customer": "Unassign from customer", + "make-public": "Make dashboard public", + "make-private": "Make dashboard private", + "manage-assigned-customers": "Manage assigned customers", + "assigned-customers": "Assigned customers", + "assign-to-customers": "Assign Dashboard(s) To Customers", + "assign-to-customers-text": "Please select the customers to assign the dashboard(s)", + "unassign-from-customers": "Unassign Dashboard(s) From Customers", + "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)", + "no-dashboards-text": "No dashboards found", + "no-widgets": "No widgets configured", + "add-widget": "Add new widget", + "title": "Title", + "select-widget-title": "Select widget", + "select-widget-subtitle": "List of available widget types", + "delete": "Delete dashboard", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "dashboard-details": "Dashboard details", + "add-dashboard-text": "Add new dashboard", + "assign-dashboards": "Assign dashboards", + "assign-new-dashboard": "Assign new dashboard", + "assign-dashboards-text": "Assign { count, plural, 1 {1 dashboard} other {# dashboards} } to customers", + "unassign-dashboards-action-text": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customers", + "delete-dashboards": "Delete dashboards", + "unassign-dashboards": "Unassign dashboards", + "unassign-dashboards-action-title": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customer", + "delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.", + "delete-dashboards-title": "Are you sure you want to delete { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Delete { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.", + "unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.", + "unassign-dashboard": "Unassign dashboard", + "unassign-dashboards-title": "Are you sure you want to unassign { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.", + "public-dashboard-title": "Dashboard is now public", + "public-dashboard-text": "Your dashboard {{dashboardTitle}} is now public and accessible via next public link:", + "public-dashboard-notice": "Note: Do not forget to make related devices public in order to access their data.", + "make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?", + "make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.", + "make-private-dashboard": "Make dashboard private", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Select dashboard", + "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.", + "dashboard-required": "Dashboard is required.", + "select-existing": "Select existing dashboard", + "create-new": "Create new dashboard", + "new-dashboard-title": "New dashboard title", + "open-dashboard": "Open dashboard", + "set-background": "Set background", + "background-color": "Background color", + "background-image": "Background image", + "background-size-mode": "Background size mode", + "no-image": "No image selected", + "drop-image": "Drop an image or click to select a file to upload.", + "settings": "Settings", + "columns-count": "Columns count", + "columns-count-required": "Columns count is required.", + "min-columns-count-message": "Only 10 minimum column count is allowed.", + "max-columns-count-message": "Only 1000 maximum column count is allowed.", + "widgets-margins": "Margin between widgets", + "horizontal-margin": "Horizontal margin", + "horizontal-margin-required": "Horizontal margin value is required.", + "min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.", + "max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.", + "vertical-margin": "Vertical margin", + "vertical-margin-required": "Vertical margin value is required.", + "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", + "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", + "autofill-height": "Auto fill layout height", + "mobile-layout": "Mobile layout settings", + "mobile-row-height": "Mobile row height, px", + "mobile-row-height-required": "Mobile row height value is required.", + "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", + "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", + "display-title": "Display dashboard title", + "toolbar-always-open": "Keep toolbar opened", + "title-color": "Title color", + "display-dashboards-selection": "Display dashboards selection", + "display-entities-selection": "Display entities selection", + "display-dashboard-timewindow": "Display timewindow", + "display-dashboard-export": "Display export", + "import": "Import dashboard", + "export": "Export dashboard", + "export-failed-error": "Unable to export dashboard: {{error}}", + "create-new-dashboard": "Create new dashboard", + "dashboard-file": "Dashboard file", + "invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.", + "dashboard-import-missing-aliases-title": "Configure aliases used by imported dashboard", + "create-new-widget": "Create new widget", + "import-widget": "Import widget", + "widget-file": "Widget file", + "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.", + "widget-import-missing-aliases-title": "Configure aliases used by imported widget", + "open-toolbar": "Open dashboard toolbar", + "close-toolbar": "Close toolbar", + "configuration-error": "Configuration error", + "alias-resolution-error-title": "Dashboard aliases configuration error", + "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.
Please contact your administrator in order to resolve this issue.", + "select-devices": "Select devices", + "assignedToCustomer": "Assigned to customer", + "assignedToCustomers": "Assigned to customers", + "public": "Public", + "public-link": "Public link", + "copy-public-link": "Copy public link", + "public-link-copied-message": "Dashboard public link has been copied to clipboard", + "manage-states": "Manage dashboard states", + "states": "Dashboard states", + "search-states": "Search dashboard states", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected", + "edit-state": "Edit dashboard state", + "delete-state": "Delete dashboard state", + "add-state": "Add dashboard state", + "state": "Dashboard state", + "state-name": "Name", + "state-name-required": "Dashboard state name is required.", + "state-id": "State Id", + "state-id-required": "Dashboard state id is required.", + "state-id-exists": "Dashboard state with the same id is already exists.", + "is-root-state": "Root state", + "delete-state-title": "Delete dashboard state", + "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", + "show-details": "Show details", + "hide-details": "Hide details", + "select-state": "Select target state", + "state-controller": "State controller" + }, + "datakey": { + "settings": "Settings", + "advanced": "Advanced", + "label": "Label", + "color": "Color", + "units": "Special symbol to show next to value", + "decimals": "Number of digits after floating point", + "data-generation-func": "Data generation function", + "use-data-post-processing-func": "Use data post-processing function", + "configuration": "Data key configuration", + "timeseries": "Timeseries", + "attributes": "Attributes", + "alarm": "Alarm fields", + "timeseries-required": "Entity timeseries are required.", + "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", + "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Alarm fields are required.", + "function-types": "Function types", + "function-types-required": "Function types are required.", + "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "timestamp of the current value;", + "value-description": "the current value;", + "prev-value-description": "result of the previous function call;", + "time-prev-description": "timestamp of the previous value;", + "prev-orig-value-description": "original previous value;" + }, + "datasource": { + "type": "Datasource type", + "name": "Name", + "add-datasource-prompt": "Please add datasource" + }, + "details": { + "edit-mode": "Edit mode", + "toggle-edit-mode": "Toggle edit mode" + }, + "device": { + "device": "Device", + "device-required": "Device is required.", + "devices": "Devices", + "management": "Device management", + "view-devices": "View Devices", + "device-alias": "Device alias", + "aliases": "Device aliases", + "no-alias-matching": "'{{alias}}' not found.", + "no-aliases-found": "No aliases found.", + "no-key-matching": "'{{key}}' not found.", + "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}}'.
Device aliases must be unique whithin the dashboard.", + "configure-alias": "Configure '{{alias}}' alias", + "no-devices-matching": "No devices matching '{{entity}}' were found.", + "alias": "Alias", + "alias-required": "Device alias is required.", + "remove-alias": "Remove device alias", + "add-alias": "Add device alias", + "name-starts-with": "Device name starts with", + "device-list": "Device list", + "use-device-name-filter": "Use filter", + "device-list-empty": "No devices selected.", + "device-name-filter-required": "Device name filter is required.", + "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.", + "add": "Add Device", + "assign-to-customer": "Assign to customer", + "assign-device-to-customer": "Assign Device(s) To Customer", + "assign-device-to-customer-text": "Please select the devices to assign to the customer", + "make-public": "Make device public", + "make-private": "Make device private", + "no-devices-text": "No devices found", + "assign-to-customer-text": "Please select the customer to assign the device(s)", + "device-details": "Device details", + "add-device-text": "Add new device", + "credentials": "Credentials", + "manage-credentials": "Manage credentials", + "delete": "Delete device", + "assign-devices": "Assign devices", + "assign-devices-text": "Assign { count, plural, 1 {1 device} other {# devices} } to customer", + "delete-devices": "Delete devices", + "unassign-from-customer": "Unassign from customer", + "unassign-devices": "Unassign devices", + "unassign-devices-action-title": "Unassign { count, plural, 1 {1 device} other {# devices} } from customer", + "assign-new-device": "Assign new device", + "make-public-device-title": "Are you sure you want to make the device '{{deviceName}}' public?", + "make-public-device-text": "After the confirmation the device and all its data will be made public and accessible by others.", + "make-private-device-title": "Are you sure you want to make the device '{{deviceName}}' private?", + "make-private-device-text": "After the confirmation the device and all its data will be made private and won't be accessible by others.", + "view-credentials": "View credentials", + "delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?", + "delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.", + "delete-devices-title": "Are you sure you want to delete { count, plural, 1 {1 device} other {# devices} }?", + "delete-devices-action-title": "Delete { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.", + "unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?", + "unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.", + "unassign-device": "Unassign device", + "unassign-devices-title": "Are you sure you want to unassign { count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.", + "device-credentials": "Device Credentials", + "credentials-type": "Credentials type", + "access-token": "Access token", + "access-token-required": "Access token is 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 is required.", + "secret": "Secret", + "secret-required": "Secret is required.", + "device-type": "Device type", + "device-type-required": "Device type is required.", + "select-device-type": "Select device type", + "enter-device-type": "Enter device type", + "any-device": "Any device", + "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", + "device-type-list-empty": "No device types selected.", + "device-types": "Device types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "label": "Label", + "events": "Events", + "details": "Details", + "copyId": "Copy device Id", + "copyAccessToken": "Copy access token", + "idCopiedMessage": "Device Id has been copied to clipboard", + "accessTokenCopiedMessage": "Device access token has been copied to clipboard", + "assignedToCustomer": "Assigned to customer", + "unable-delete-device-alias-title": "Unable to delete device alias", + "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "is-gateway": "Is gateway", + "public": "Public", + "device-public": "Device is public", + "select-device": "Select device", + "import": "Import device", + "device-file": "Device file" + }, + "dialog": { + "close": "Close dialog" + }, + "direction": { + "column": "Column", + "row": "Row" + }, + "error": { + "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", + "unhandled-error-code": "Unhandled error code: {{errorCode}}", + "unknown-error": "Unknown error" + }, + "entity": { + "entity": "Entity", + "entities": "Entities", + "aliases": "Entity aliases", + "entity-alias": "Entity alias", + "unable-delete-entity-alias-title": "Unable to delete entity alias", + "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity aliases must be unique whithin the dashboard.", + "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", + "configure-alias": "Configure '{{alias}}' alias", + "alias": "Alias", + "alias-required": "Entity alias is required.", + "remove-alias": "Remove entity alias", + "add-alias": "Add entity alias", + "entity-list": "Entity list", + "entity-type": "Entity type", + "entity-types": "Entity types", + "entity-type-list": "Entity type list", + "any-entity": "Any entity", + "enter-entity-type": "Enter entity type", + "no-entities-matching": "No entities matching '{{entity}}' were found.", + "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", + "name-starts-with": "Name starts with", + "use-entity-name-filter": "Use filter", + "entity-list-empty": "No entities selected.", + "entity-type-list-empty": "No entity types selected.", + "entity-name-filter-required": "Entity name filter is required.", + "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", + "all-subtypes": "All", + "select-entities": "Select entities", + "no-aliases-found": "No aliases found.", + "no-alias-matching": "'{{alias}}' not found.", + "create-new-alias": "Create a new one!", + "key": "Key", + "key-name": "Key name", + "no-keys-found": "No keys found.", + "no-key-matching": "'{{key}}' not found.", + "create-new-key": "Create a new one!", + "type": "Type", + "type-required": "Entity type is required.", + "type-device": "Device", + "type-devices": "Devices", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Devices whose names start 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 whose names start with '{{prefix}}'", + "type-entity-view": "Entity View", + "type-entity-views": "Entity Views", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", + "entity-view-name-starts-with": "Entity Views whose names start with '{{prefix}}'", + "type-rule": "Rule", + "type-rules": "Rules", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", + "type-customer": "Customer", + "type-customers": "Customers", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", + "type-user": "User", + "type-users": "Users", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Users whose names start 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 whose names start with '{{prefix}}'", + "type-alarm": "Alarm", + "type-alarms": "Alarms", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", + "type-rulechain": "Rule chain", + "type-rulechains": "Rule chains", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'", + "type-rulenode": "Rule node", + "type-rulenodes": "Rule nodes", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'", + "type-current-customer": "Current Customer", + "search": "Search entities", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", + "entity-name": "Entity name", + "details": "Entity details", + "no-entities-prompt": "No entities found", + "no-data": "No data to display", + "columns-to-display": "Columns to Display" + }, + "entity-view": { + "entity-view": "Entity View", + "entity-view-required": "Entity view is required.", + "entity-views": "Entity Views", + "management": "Entity View management", + "view-entity-views": "View Entity Views", + "entity-view-alias": "Entity View alias", + "aliases": "Entity View aliases", + "no-alias-matching": "'{{alias}}' not found.", + "no-aliases-found": "No aliases found.", + "no-key-matching": "'{{key}}' not found.", + "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 within the dashboard.", + "configure-alias": "Configure '{{alias}}' alias", + "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", + "alias": "Alias", + "alias-required": "Entity View alias is required.", + "remove-alias": "Remove entity view alias", + "add-alias": "Add entity view alias", + "name-starts-with": "Entity View name starts with", + "entity-view-list": "Entity View list", + "use-entity-view-name-filter": "Use filter", + "entity-view-list-empty": "No entity views selected.", + "entity-view-name-filter-required": "Entity view name filter is required.", + "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", + "add": "Add Entity View", + "assign-to-customer": "Assign to customer", + "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", + "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", + "no-entity-views-text": "No entity views found", + "assign-to-customer-text": "Please select the customer to assign the entity view(s)", + "entity-view-details": "Entity view details", + "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 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 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 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 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.", + "select-entity-view-type": "Select entity view type", + "enter-entity-view-type": "Enter entity view type", + "any-entity-view": "Any entity view", + "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", + "entity-view-type-list-empty": "No entity view types selected.", + "entity-view-types": "Entity View types", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "events": "Events", + "details": "Details", + "copyId": "Copy entity view Id", + "assignedToCustomer": "Assigned to customer", + "unable-entity-view-device-alias-title": "Unable to delete entity view alias", + "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", + "select-entity-view": "Select entity view", + "make-public": "Make entity view public", + "make-private": "Make entity view private", + "start-date": "Start date", + "start-ts": "Start time", + "end-date": "End date", + "end-ts": "End time", + "date-limits": "Date limits", + "client-attributes": "Client attributes", + "shared-attributes": "Shared attributes", + "server-attributes": "Server attributes", + "timeseries": "Timeseries", + "client-attributes-placeholder": "Client attributes", + "shared-attributes-placeholder": "Shared attributes", + "server-attributes-placeholder": "Server attributes", + "timeseries-placeholder": "Timeseries", + "target-entity": "Target entity", + "attributes-propagation": "Attributes propagation", + "attributes-propagation-hint": "Entity View will automatically copy specified attributes from Target Entity each time you save or update this entity view. For performance reasons target entity attributes are not propagated to entity view on each attribute change. You can enable automatic propagation by configuring \"copy to view\" rule node in your rule chain and linking \"Post attributes\" and \"Attributes Updated\" messages to the new rule node.", + "timeseries-data": "Timeseries data", + "timeseries-data-hint": "Configure timeseries data keys of the target entity that will be accessible to the entity view. This timeseries data is read-only.", + "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", + "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", + "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", + "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others." + }, + "event": { + "event-type": "Event type", + "type-error": "Error", + "type-lc-event": "Lifecycle event", + "type-stats": "Statistics", + "type-debug-rule-node": "Debug", + "type-debug-rule-chain": "Debug", + "no-events-prompt": "No events found", + "error": "Error", + "alarm": "Alarm", + "event-time": "Event time", + "server": "Server", + "body": "Body", + "method": "Method", + "type": "Type", + "entity": "Entity", + "message-id": "Message Id", + "message-type": "Message Type", + "data-type": "Data Type", + "relation-type": "Relation Type", + "metadata": "Metadata", + "data": "Data", + "event": "Event", + "status": "Status", + "success": "Success", + "failed": "Failed", + "messages-processed": "Messages processed", + "errors-occurred": "Errors occurred" + }, + "extension": { + "extensions": "Extensions", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selected", + "type": "Type", + "key": "Key", + "value": "Value", + "id": "Id", + "extension-id": "Extension id", + "extension-type": "Extension type", + "transformer-json": "JSON *", + "unique-id-required": "Current extension id already exists.", + "delete": "Delete extension", + "add": "Add extension", + "edit": "Edit extension", + "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?", + "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.", + "delete-extensions-title": "Are you sure you want to delete { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", + "converters": "Converters", + "converter-id": "Converter id", + "configuration": "Configuration", + "converter-configurations": "Converter configurations", + "token": "Security token", + "add-converter": "Add converter", + "add-config": "Add converter configuration", + "device-name-expression": "Device name expression", + "device-type-expression": "Device type expression", + "custom": "Custom", + "to-double": "To Double", + "transformer": "Transformer", + "json-required": "Transformer json is required.", + "json-parse": "Unable to parse transformer json.", + "attributes": "Attributes", + "add-attribute": "Add attribute", + "add-map": "Add mapping element", + "timeseries": "Timeseries", + "add-timeseries": "Add timeseries", + "field-required": "Field is required", + "brokers": "Brokers", + "add-broker": "Add broker", + "host": "Host", + "port": "Port", + "port-range": "Port should be in a range from 1 to 65535.", + "ssl": "Ssl", + "credentials": "Credentials", + "username": "Username", + "password": "Password", + "retry-interval": "Retry interval in milliseconds", + "anonymous": "Anonymous", + "basic": "Basic", + "pem": "PEM", + "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.", + "mapping": "Mapping", + "topic-filter": "Topic filter", + "converter-type": "Converter type", + "converter-json": "Json", + "json-name-expression": "Device name json expression", + "topic-name-expression": "Device name topic expression", + "json-type-expression": "Device type json expression", + "topic-type-expression": "Device type topic expression", + "attribute-key-expression": "Attribute key expression", + "attr-json-key-expression": "Attribute key json expression", + "attr-topic-key-expression": "Attribute key topic expression", + "request-id-expression": "Request id expression", + "request-id-json-expression": "Request id json expression", + "request-id-topic-expression": "Request id topic expression", + "response-topic-expression": "Response topic expression", + "value-expression": "Value expression", + "topic": "Topic", + "timeout": "Timeout in milliseconds", + "converter-json-required": "Converter json is required.", + "converter-json-parse": "Unable to parse converter json.", + "filter-expression": "Filter expression", + "connect-requests": "Connect requests", + "add-connect-request": "Add connect request", + "disconnect-requests": "Disconnect requests", + "add-disconnect-request": "Add disconnect request", + "attribute-requests": "Attribute requests", + "add-attribute-request": "Add attribute request", + "attribute-updates": "Attribute updates", + "add-attribute-update": "Add attribute update", + "server-side-rpc": "Server side RPC", + "add-server-side-rpc-request": "Add server-side RPC request", + "device-name-filter": "Device name filter", + "attribute-filter": "Attribute filter", + "method-filter": "Method filter", + "request-topic-expression": "Request topic expression", + "response-timeout": "Response timeout in milliseconds", + "topic-expression": "Topic expression", + "client-scope": "Client scope", + "add-device": "Add device", + "opc-server": "Servers", + "opc-add-server": "Add server", + "opc-add-server-prompt": "Please add server", + "opc-application-name": "Application name", + "opc-application-uri": "Application uri", + "opc-scan-period-in-seconds": "Scan period in seconds", + "opc-security": "Security", + "opc-identity": "Identity", + "opc-keystore": "Keystore", + "opc-type": "Type", + "opc-keystore-type": "Type", + "opc-keystore-location": "Location *", + "opc-keystore-password": "Password", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Key password", + "opc-device-node-pattern": "Device node pattern", + "opc-device-name-pattern": "Device name pattern", + "modbus-server": "Servers/slaves", + "modbus-add-server": "Add server/slave", + "modbus-add-server-prompt": "Please add server/slave", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Automatically reconnect", + "modbus-rtu-over-tcp": "RTU over TCP", + "modbus-port-name": "Serial port name", + "modbus-encoding": "Encoding", + "modbus-parity": "Parity", + "modbus-baudrate": "Baud rate", + "modbus-databits": "Data bits", + "modbus-stopbits": "Stop bits", + "modbus-databits-range": "Data bits should be in a range from 7 to 8.", + "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", + "modbus-unit-id": "Unit ID", + "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", + "modbus-device-name": "Device name", + "modbus-poll-period": "Poll period (ms)", + "modbus-attributes-poll-period": "Attributes poll period (ms)", + "modbus-timeseries-poll-period": "Timeseries poll period (ms)", + "modbus-poll-period-range": "Poll period should be positive value.", + "modbus-tag": "Tag", + "modbus-function": "Function", + "modbus-register-address": "Register address", + "modbus-register-address-range": "Register address should be in a range from 0 to 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", + "modbus-register-count": "Register count", + "modbus-register-count-range": "Register count should be a positive value.", + "modbus-byte-order": "Byte order", + "sync": { + "status": "Status", + "sync": "Sync", + "not-sync": "Not sync", + "last-sync-time": "Last sync time", + "not-available": "Not available" + }, + "export-extensions-configuration": "Export extensions configuration", + "import-extensions-configuration": "Import extensions configuration", + "import-extensions": "Import extensions", + "import-extension": "Import extension", + "export-extension": "Export extension", + "file": "Extensions file", + "invalid-file-error": "Invalid extension file" + }, + "fullscreen": { + "expand": "Expand to fullscreen", + "exit": "Exit fullscreen", + "toggle": "Toggle fullscreen mode", + "fullscreen": "Fullscreen" + }, + "function": { + "function": "Function" + }, + "grid": { + "delete-item-title": "Are you sure you want to delete this item?", + "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", + "delete-items-title": "Are you sure you want to delete { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Delete { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.", + "add-item-text": "Add new item", + "no-items-text": "No items found", + "item-details": "Item details", + "delete-item": "Delete Item", + "delete-items": "Delete Items", + "scroll-to-top": "Scroll to top" + }, + "help": { + "goto-help-page": "Go to help page" + }, + "home": { + "home": "Home", + "profile": "Profile", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Open user menu" + }, + "import": { + "no-file": "No file selected", + "drop-file": "Drop a JSON file or click to select a file to upload.", + "drop-file-csv": "Drop a CSV file or click to select a file to upload.", + "column-value": "Value", + "column-title": "Title", + "column-example": "Example value data", + "column-key": "Attribute/telemetry key", + "csv-delimiter": "CSV delimiter", + "csv-first-line-header": "First line contains column names", + "csv-update-data": "Update attributes/telemetry", + "import-csv-number-columns-error": "A file should contain at least two columns", + "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", + "column-type": { + "name": "Name", + "type": "Type", + "column-type": "Column type", + "client-attribute": "Client attribute", + "shared-attribute": "Shared attribute", + "server-attribute": "Server attribute", + "timeseries": "Timeseries", + "entity-field": "Entity field", + "access-token": "Access token" + }, + "stepper-text": { + "select-file": "Select a file", + "configuration": "Import configuration", + "column-type": "Select columns type", + "creat-entities": "Creating new entities", + "done": "Done" + }, + "message": { + "create-entities": "{{count}} new entities were successfully created.", + "update-entities": "{{count}} entities were successfully updated.", + "error-entities": "There was an error creating {{count}} entities." + } + }, + "item": { + "selected": "Selected" + }, + "js-func": { + "no-return-error": "Function must return value!", + "return-type-mismatch": "Function must return value of '{{type}}' type!", + "tidy": "Tidy" + }, + "key-val": { + "key": "Key", + "value": "Value", + "remove-entry": "Remove entry", + "add-entry": "Add entry", + "no-data": "No entries" + }, + "layout": { + "layout": "Layout", + "manage": "Manage layouts", + "settings": "Layout settings", + "color": "Color", + "main": "Main", + "right": "Right", + "select": "Select target layout" + }, + "legend": { + "direction": "Legend direction", + "position": "Legend position", + "show-max": "Show max value", + "show-min": "Show min value", + "show-avg": "Show average value", + "show-total": "Show total value", + "settings": "Legend settings", + "min": "min", + "max": "max", + "avg": "avg", + "total": "total" + }, + "login": { + "login": "Login", + "request-password-reset": "Request Password Reset", + "reset-password": "Reset Password", + "create-password": "Create Password", + "passwords-mismatch-error": "Entered passwords must be same!", + "password-again": "Password again", + "sign-in": "Please sign in", + "username": "Username (email)", + "remember-me": "Remember me", + "forgot-password": "Forgot Password?", + "password-reset": "Password reset", + "new-password": "New password", + "new-password-again": "New password again", + "password-link-sent-message": "Password reset link was successfully sent!", + "email": "Email" + }, + "position": { + "top": "Top", + "bottom": "Bottom", + "left": "Left", + "right": "Right" + }, + "profile": { + "profile": "Profile", + "change-password": "Change Password", + "current-password": "Current password" + }, + "relation": { + "relations": "Relations", + "direction": "Direction", + "search-direction": { + "FROM": "From", + "TO": "To" + }, + "direction-type": { + "FROM": "from", + "TO": "to" + }, + "from-relations": "Outbound relations", + "to-relations": "Inbound relations", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected", + "type": "Type", + "to-entity-type": "To entity type", + "to-entity-name": "To entity name", + "from-entity-type": "From entity type", + "from-entity-name": "From entity name", + "to-entity": "To entity", + "from-entity": "From entity", + "delete": "Delete relation", + "relation-type": "Relation type", + "relation-type-required": "Relation type is required.", + "any-relation-type": "Any type", + "add": "Add relation", + "edit": "Edit relation", + "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", + "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", + "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.", + "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", + "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", + "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", + "remove-relation-filter": "Remove relation filter", + "add-relation-filter": "Add relation filter", + "any-relation": "Any relation", + "relation-filters": "Relation filters", + "additional-info": "Additional info (JSON)", + "invalid-additional-info": "Unable to parse additional info json." + }, + "rulechain": { + "rulechain": "Rule chain", + "rulechains": "Rule chains", + "root": "Root", + "delete": "Delete rule chain", + "name": "Name", + "name-required": "Name is required.", + "description": "Description", + "add": "Add Rule Chain", + "set-root": "Make rule chain root", + "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", + "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", + "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?", + "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.", + "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.", + "add-rulechain-text": "Add new rule chain", + "no-rulechains-text": "No rule chains found", + "rulechain-details": "Rule chain details", + "details": "Details", + "events": "Events", + "system": "System", + "import": "Import rule chain", + "export": "Export rule chain", + "export-failed-error": "Unable to export rule chain: {{error}}", + "create-new-rulechain": "Create new rule chain", + "rulechain-file": "Rule chain file", + "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", + "copyId": "Copy rule chain Id", + "idCopiedMessage": "Rule chain Id has been copied to clipboard", + "select-rulechain": "Select rule chain", + "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", + "rulechain-required": "Rule chain is required", + "management": "Rules management", + "debug-mode": "Debug mode" + }, + "rulenode": { + "details": "Details", + "events": "Events", + "search": "Search nodes", + "open-node-library": "Open node library", + "add": "Add rule node", + "name": "Name", + "name-required": "Name is required.", + "type": "Type", + "description": "Description", + "delete": "Delete rule node", + "select-all-objects": "Select all nodes and connections", + "deselect-all-objects": "Deselect all nodes and connections", + "delete-selected-objects": "Delete selected nodes and connections", + "delete-selected": "Delete selected", + "select-all": "Select all", + "copy-selected": "Copy selected", + "deselect-all": "Deselect all", + "rulenode-details": "Rule node details", + "debug-mode": "Debug mode", + "configuration": "Configuration", + "link": "Link", + "link-details": "Rule node link details", + "add-link": "Add link", + "link-label": "Link label", + "link-label-required": "Link label is required.", + "custom-link-label": "Custom link label", + "custom-link-label-required": "Custom link label is required.", + "link-labels": "Link labels", + "link-labels-required": "Link labels is required.", + "no-link-labels-found": "No link labels found", + "no-link-label-matching": "'{{label}}' not found.", + "create-new-link-label": "Create a new one!", + "type-filter": "Filter", + "type-filter-details": "Filter incoming messages with configured conditions", + "type-enrichment": "Enrichment", + "type-enrichment-details": "Add additional information into Message Metadata", + "type-transformation": "Transformation", + "type-transformation-details": "Change Message payload and Metadata", + "type-action": "Action", + "type-action-details": "Perform special action", + "type-external": "External", + "type-external-details": "Interacts with external system", + "type-rule-chain": "Rule Chain", + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", + "type-input": "Input", + "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", + "type-unknown": "Unknown", + "type-unknown-details": "Unresolved Rule Node", + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", + "ui-resources-load-error": "Failed to load configuration ui resources.", + "invalid-target-rulechain": "Unable to resolve target rule chain!", + "test-script-function": "Test script function", + "message": "Message", + "message-type": "Message type", + "select-message-type": "Select message type", + "message-type-required": "Message type is required", + "metadata": "Metadata", + "metadata-required": "Metadata entries can't be empty.", + "output": "Output", + "test": "Test", + "help": "Help", + "reset-debug-mode": "Reset debug mode in all nodes" + }, + "tenant": { + "tenant": "Tenant", + "tenants": "Tenants", + "management": "Tenant management", + "add": "Add Tenant", + "admins": "Admins", + "manage-tenant-admins": "Manage tenant admins", + "delete": "Delete tenant", + "add-tenant-text": "Add new tenant", + "no-tenants-text": "No tenants found", + "tenant-details": "Tenant details", + "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?", + "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.", + "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "Delete { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.", + "title": "Title", + "title-required": "Title is required.", + "description": "Description", + "details": "Details", + "events": "Events", + "copyId": "Copy tenant Id", + "idCopiedMessage": "Tenant Id has been copied to clipboard", + "select-tenant": "Select tenant", + "no-tenants-matching": "No tenants matching '{{entity}}' were found.", + "tenant-required": "Tenant is required" + }, + "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": "Days", + "hours": "Hours", + "minutes": "Minutes", + "seconds": "Seconds", + "advanced": "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": "Realtime", + "history": "History", + "last-prefix": "last", + "period": "from {{ startTime }} to {{ endTime }}", + "edit": "Edit timewindow", + "date-range": "Date range", + "last": "Last", + "time-period": "Time period" + }, + "user": { + "user": "User", + "users": "Users", + "customer-users": "Customer Users", + "tenant-admins": "Tenant Admins", + "sys-admin": "System administrator", + "tenant-admin": "Tenant administrator", + "customer": "Customer", + "anonymous": "Anonymous", + "add": "Add User", + "delete": "Delete user", + "add-user-text": "Add new user", + "no-users-text": "No users found", + "user-details": "User details", + "delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?", + "delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.", + "delete-users-title": "Are you sure you want to delete { count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "Delete { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.", + "activation-email-sent-message": "Activation email was successfully sent!", + "resend-activation": "Resend activation", + "email": "Email", + "email-required": "Email is required.", + "invalid-email-format": "Invalid email format.", + "first-name": "First Name", + "last-name": "Last Name", + "description": "Description", + "default-dashboard": "Default dashboard", + "always-fullscreen": "Always fullscreen", + "select-user": "Select user", + "no-users-matching": "No users matching '{{entity}}' were found.", + "user-required": "User is required", + "activation-method": "Activation method", + "display-activation-link": "Display activation link", + "send-activation-mail": "Send activation mail", + "activation-link": "User activation link", + "activation-link-text": "In order to activate user use the following activation link :", + "copy-activation-link": "Copy activation link", + "activation-link-copied-message": "User activation link has been copied to clipboard", + "details": "Details", + "login-as-tenant-admin": "Login as Tenant Admin", + "login-as-customer-user": "Login as Customer User" + }, + "value": { + "type": "Value type", + "string": "String", + "string-value": "String value", + "integer": "Integer", + "integer-value": "Integer value", + "invalid-integer-value": "Invalid integer value", + "double": "Double", + "double-value": "Double value", + "boolean": "Boolean", + "boolean-value": "Boolean value", + "false": "False", + "true": "True", + "long": "Long" + }, + "widget": { + "widget-library": "Widgets Library", + "widget-bundle": "Widgets Bundle", + "select-widgets-bundle": "Select widgets bundle", + "management": "Widget management", + "editor": "Widget Editor", + "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", + "widget-type-load-error": "Widget wasn't loaded due to the following errors:", + "remove": "Remove widget", + "edit": "Edit widget", + "remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?", + "remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.", + "timeseries": "Time series", + "search-data": "Search data", + "no-data-found": "No data found", + "latest-values": "Latest values", + "rpc": "Control widget", + "alarm": "Alarm widget", + "static": "Static widget", + "select-widget-type": "Select widget type", + "missing-widget-title-error": "Widget title must be specified!", + "widget-saved": "Widget saved", + "unable-to-save-widget-error": "Unable to save widget! Widget has errors!", + "save": "Save widget", + "saveAs": "Save widget as", + "save-widget-type-as": "Save widget type as", + "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle", + "toggle-fullscreen": "Toggle fullscreen", + "run": "Run widget", + "title": "Widget title", + "title-required": "Widget title is required.", + "type": "Widget type", + "resources": "Resources", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Remove resource", + "add-resource": "Add resource", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Settings schema", + "datakey-settings-schema": "Data key settings schema", + "javascript": "Javascript", + "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?", + "remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.", + "remove-widget-type": "Remove widget type", + "add-widget-type": "Add new widget type", + "widget-type-load-failed-error": "Failed to load widget type!", + "widget-template-load-failed-error": "Failed to load widget template!", + "add": "Add Widget", + "undo": "Undo widget changes", + "export": "Export widget" + }, + "widget-action": { + "header-button": "Widget header button", + "open-dashboard-state": "Navigate to new dashboard state", + "update-dashboard-state": "Update current dashboard state", + "open-dashboard": "Navigate to other dashboard", + "custom": "Custom action", + "target-dashboard-state": "Target dashboard state", + "target-dashboard-state-required": "Target dashboard state is required", + "set-entity-from-widget": "Set entity from widget", + "target-dashboard": "Target dashboard", + "open-right-layout": "Open right dashboard layout (mobile view)" + }, + "widgets-bundle": { + "current": "Current bundle", + "widgets-bundles": "Widgets Bundles", + "add": "Add Widgets Bundle", + "delete": "Delete widgets bundle", + "title": "Title", + "title-required": "Title is required.", + "add-widgets-bundle-text": "Add new widgets bundle", + "no-widgets-bundles-text": "No widgets bundles found", + "empty": "Widgets bundle is empty", + "details": "Details", + "widgets-bundle-details": "Widgets bundle details", + "delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.", + "delete-widgets-bundles-title": "Are you sure you want to delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "Delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.", + "no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.", + "widgets-bundle-required": "Widgets bundle is required.", + "system": "System", + "import": "Import widgets bundle", + "export": "Export widgets bundle", + "export-failed-error": "Unable to export widgets bundle: {{error}}", + "create-new-widgets-bundle": "Create new widgets bundle", + "widgets-bundle-file": "Widgets bundle file", + "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure." + }, + "widget-config": { + "data": "Data", + "settings": "Settings", + "advanced": "Advanced", + "title": "Title", + "general-settings": "General settings", + "display-title": "Display title", + "drop-shadow": "Drop shadow", + "enable-fullscreen": "Enable fullscreen", + "background-color": "Background color", + "text-color": "Text color", + "padding": "Padding", + "margin": "Margin", + "widget-style": "Widget style", + "title-style": "Title style", + "mobile-mode-settings": "Mobile mode settings", + "order": "Order", + "height": "Height", + "units": "Special symbol to show next to value", + "decimals": "Number of digits after floating point", + "timewindow": "Timewindow", + "use-dashboard-timewindow": "Use dashboard timewindow", + "display-timewindow": "Display timewindow", + "display-legend": "Display legend", + "datasources": "Datasources", + "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "Type", + "datasource-parameters": "Parameters", + "remove-datasource": "Remove datasource", + "add-datasource": "Add datasource", + "target-device": "Target device", + "alarm-source": "Alarm source", + "actions": "Actions", + "action": "Action", + "add-action": "Add action", + "search-actions": "Search actions", + "action-source": "Action source", + "action-source-required": "Action source is required.", + "action-name": "Name", + "action-name-required": "Action name is required.", + "action-name-not-unique": "Another action with the same name already exists.
Action name should be unique within the same action source.", + "action-icon": "Icon", + "action-type": "Type", + "action-type-required": "Action type is required.", + "edit-action": "Edit action", + "delete-action": "Delete action", + "delete-action-title": "Delete widget action", + "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?" + }, + "widget-type": { + "import": "Import widget type", + "export": "Export widget type", + "export-failed-error": "Unable to export widget type: {{error}}", + "create-new-widget-type": "Create new widget type", + "widget-type-file": "Widget type file", + "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Sun", + "Mon": "Mon", + "Tue": "Tue", + "Wed": "Wed", + "Thu": "Thu", + "Fri": "Fri", + "Sat": "Sat", + "Jan": "Jan", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "May", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "January": "January", + "February": "February", + "March": "March", + "April": "April", + "June": "June", + "July": "July", + "August": "August", + "September": "September", + "October": "October", + "November": "November", + "December": "December", + "Custom Date Range": "Custom Date Range", + "Date Range Template": "Date Range Template", + "Today": "Today", + "Yesterday": "Yesterday", + "This Week": "This Week", + "Last Week": "Last Week", + "This Month": "This Month", + "Last Month": "Last Month", + "Year": "Year", + "This Year": "This Year", + "Last Year": "Last Year", + "Date picker": "Date picker", + "Hour": "Hour", + "Day": "Day", + "Week": "Week", + "2 weeks": "2 Weeks", + "Month": "Month", + "3 months": "3 Months", + "6 months": "6 Months", + "Custom interval": "Custom interval", + "Interval": "Interval", + "Step size": "Step size", + "Ok": "Ok" + } + } + }, + "icon": { + "icon": "Icon", + "select-icon": "Select icon", + "material-icons": "Material icons", + "show-all": "Show all icons" + }, + "custom": { + "widget-action": { + "action-cell-button": "Action cell button", + "row-click": "On row click", + "polygon-click": "On polygon click", + "marker-click": "On marker click", + "tooltip-tag-action": "Tooltip tag action", + "node-selected": "On node selected", + "element-click": "On HTML element click" + } + }, + "language": { + "language": "Language", + "locales": { + "de_DE": "German", + "fr_FR": "French", + "zh_CN": "Simplified Chinese", + "en_US": "English", + "it_IT": "Italian", + "ko_KR": "Korean", + "ru_RU": "Russian", + "es_ES": "Spanish", + "ja_JA": "Japanese", + "tr_TR": "Turkish", + "fa_IR": "Persian", + "uk_UA": "Ukrainian", + "cs_CZ": "Czech" + } + } +} \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 25257232d0..87075014ba 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1801,7 +1801,8 @@ "fa_IR": "Персидский", "uk_UA": "Украинский", "cs_CZ": "Чешский", - "el_GR": "Греческий" + "el_GR": "Греческий", + "lv_LV": "Латышский" } } } diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index 69d9047dd8..c9c5964c52 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -1605,7 +1605,8 @@ "fa_IR": "Farsça", "uk_UA": "Ukrayna", "cs_CZ": "Çekçe", - "el_GR": "Yunanca" + "el_GR": "Yunanca", + "lv_LV": "Letonca" } } } \ 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 bc64b52901..6aa1e06e89 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -2407,7 +2407,8 @@ "uk_UA": "Українська", "fa_IR": "Перська", "cs_CZ": "Чеська", - "el_GR": "Грецька" + "el_GR": "Грецька", + "lv_LV": "Латиська" } } } From fbed56555f42a3534695d3d76c8ba6f6f92ed995 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Wed, 29 Jan 2020 17:25:55 +0200 Subject: [PATCH 178/261] Feature/rest client (#2368) * refactored URLs * refactored * refactored * refactored * refactored * refactored rest client * changed executorService from RestClient * refactored rest client and JsonConverter * refactored rest client --- .../thingsboard/client/tools/RestClient.java | 218 +++++++++--------- 1 file changed, 105 insertions(+), 113 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 9b64f2292a..179902d1d3 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -1690,82 +1690,72 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return RestJsonConverter.toTimeseries(timeseries); } - public List saveDeviceAttributes(String deviceId, String scope, JsonNode request) { - List attributes = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{deviceId}/{scope}", - HttpMethod.POST, - new HttpEntity<>(request), - new ParameterizedTypeReference>() { - }, - deviceId, - scope).getBody(); - - return RestJsonConverter.toAttributes(attributes); - } - - public List saveEntityAttributesV1(EntityId entityId, String scope, JsonNode request) { - List attributes = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}", - HttpMethod.POST, - new HttpEntity<>(request), - new ParameterizedTypeReference>() { - }, - entityId.getEntityType().name(), - entityId.getId().toString(), - scope).getBody(); - - return RestJsonConverter.toAttributes(attributes); - } - - public List saveEntityAttributesV2(EntityId entityId, String scope, JsonNode request) { - List attributes = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", - HttpMethod.POST, - new HttpEntity<>(request), - new ParameterizedTypeReference>() { - }, - entityId.getEntityType().name(), - entityId.getId().toString(), - scope).getBody(); - - return RestJsonConverter.toAttributes(attributes); - } - - public List saveEntityTelemetry(EntityId entityId, String scope, String requestBody) { - Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}", - HttpMethod.POST, - new HttpEntity<>(requestBody), - new ParameterizedTypeReference>>() { - }, - entityId.getEntityType().name(), - entityId.getId().toString(), - scope).getBody(); - - return RestJsonConverter.toTimeseries(timeseries); - } - - public List saveEntityTelemetryWithTTL(EntityId entityId, String scope, Long ttl, String requestBody) { - Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}/{ttl}", - HttpMethod.POST, - new HttpEntity<>(requestBody), - new ParameterizedTypeReference>>() { - }, - entityId.getEntityType().name(), - entityId.getId().toString(), - scope, - ttl).getBody(); - - return RestJsonConverter.toTimeseries(timeseries); - } - - public List deleteEntityTimeseries(EntityId entityId, - List keys, - boolean deleteAllDataForKeys, - Long startTs, - Long endTs, - boolean rewriteLatestIfDeleted) { + public boolean saveDeviceAttributes(DeviceId deviceId, String scope, JsonNode request) { + return restTemplate + .postForEntity(baseURL + "/api/plugins/telemetry/{deviceId}/{scope}", request, Object.class, deviceId.getId().toString(), scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityAttributesV1(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityAttributesV2(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityTelemetry(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityTelemetryWithTTL(EntityId entityId, String scope, Long ttl, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}/{ttl}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope, + ttl) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean deleteEntityTimeseries(EntityId entityId, + List keys, + boolean deleteAllDataForKeys, + Long startTs, + Long endTs, + boolean rewriteLatestIfDeleted) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); @@ -1775,44 +1765,46 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params.put("endTs", endTs.toString()); params.put("rewriteLatestIfDeleted", String.valueOf(rewriteLatestIfDeleted)); - Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", - HttpMethod.DELETE, - HttpEntity.EMPTY, - new ParameterizedTypeReference>>() { - }, - params).getBody(); - - return RestJsonConverter.toTimeseries(timeseries); - } + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + params) + .getStatusCode() + .is2xxSuccessful(); + + } + + public boolean deleteEntityAttributes(DeviceId deviceId, String scope, List keys) { + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{deviceId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + deviceId.getId().toString(), + scope, + listToString(keys)) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean deleteEntityAttributes(EntityId entityId, String scope, List keys) { + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope, + listToString(keys)) + .getStatusCode() + .is2xxSuccessful(); - public List deleteEntityAttributes(String deviceId, String scope, List keys) { - List attributes = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{deviceId}/{scope}?keys={keys}", - HttpMethod.DELETE, - HttpEntity.EMPTY, - new ParameterizedTypeReference>() { - }, - deviceId, - scope, - listToString(keys)).getBody(); - - return RestJsonConverter.toAttributes(attributes); - } - - public List deleteEntityAttributes(EntityId entityId, String scope, List keys) { - List attributes = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}?keys={keys}", - HttpMethod.DELETE, - HttpEntity.EMPTY, - new ParameterizedTypeReference>() { - }, - entityId.getEntityType().name(), - entityId.getId().toString(), - scope, - listToString(keys)).getBody(); - - return RestJsonConverter.toAttributes(attributes); } public Optional getTenantById(String tenantId) { From e3b39bedf691ef5b1e057c29d2c1fa72d185fa60 Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Thu, 30 Jan 2020 11:11:50 +0200 Subject: [PATCH 179/261] WIP_Gate way form (#2370) * gateWayForm: start branch * gateWayForm: start branch2 * gateWayForm: start add new form to gateway_widgets.json * gateWayForm: start add new logs.conf * Fix html and clear js * gateWayForm: start add new222 * improvement gateway config form (change html) * gateWayForm: new vadim verstka * GatewayForm: add valid config * GatewayForm: add valid config compile and add form to widgets library * GatewayForm: bug err yml Co-authored-by: Vladyslav --- .../widget_bundles/gateway_widgets.json | 22 +- ui/package.json | 1 + ui/src/app/common/types.constant.js | 26 + .../gateWay/gateway-config-dialog.tpl.html | 75 +++ .../gateway-config-select.directive.js | 137 +++++ .../gateWay/gateway-config-select.scss | 35 ++ .../gateWay/gateway-config-select.tpl.html | 54 ++ .../gateWay/gateway-config.directive.js | 317 +++++++++++ .../components/gateWay/gateway-config.scss | 85 +++ .../gateWay/gateway-config.tpl.html | 94 ++++ .../gateWay/gateway-form.directive.js | 498 ++++++++++++++++++ .../app/components/gateWay/gateway-form.scss | 40 ++ .../components/gateWay/gateway-form.tpl.html | 219 ++++++++ .../import-export/import-export.service.js | 51 +- ui/src/app/layout/index.js | 6 + ui/src/app/locale/locale.constant-en_US.json | 55 +- 16 files changed, 1712 insertions(+), 3 deletions(-) create mode 100644 ui/src/app/components/gateWay/gateway-config-dialog.tpl.html create mode 100644 ui/src/app/components/gateWay/gateway-config-select.directive.js create mode 100644 ui/src/app/components/gateWay/gateway-config-select.scss create mode 100644 ui/src/app/components/gateWay/gateway-config-select.tpl.html create mode 100644 ui/src/app/components/gateWay/gateway-config.directive.js create mode 100644 ui/src/app/components/gateWay/gateway-config.scss create mode 100644 ui/src/app/components/gateWay/gateway-config.tpl.html create mode 100644 ui/src/app/components/gateWay/gateway-form.directive.js create mode 100644 ui/src/app/components/gateWay/gateway-form.scss create mode 100644 ui/src/app/components/gateWay/gateway-form.tpl.html diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json index c963835ef9..ce78185c29 100644 --- a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -20,6 +20,26 @@ "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\":\"4px\",\"settings\":{},\"title\":\"Extensions table\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"18px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } + }, + { + "alias": "new_config_form", + "name": "Config form", + "descriptor": { + "type": "static", + "sizeX": 7.5, + "sizeY": 10.5, + "resources": [ + { + "url": "" + } + ], + "templateHtml": "\n\n", + "templateCss": "#container {\n overflow: auto;\n height: 100%;\n margin: auto;\n}\n\n\n\n/*#configurations {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n/* height: 100%;*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/*}*/\n\n/*.configurationPointParent {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n \n/*}*/\n\n/*.configurationPoint {*/\n/* display: flex;*/\n/* flex-direction: row;*/\n/* justify-content: space-between;*/\n/* margin: 5px;*/\n/*}*/\n\n/*.configurationPoint.select {*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n\n/*}*/\n\n/*.configurationPoint.select.inputRow {*/\n/* margin: 0px;*/\n/* width: 100%;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n/*}*/\n\n\n/*.error {*/\n/*color: red;*/\n/*}*/", + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.formId = \"form-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n self.ctx.$scope.$broadcast('gateway-form-resize', self.ctx.$scope.formId);\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"GatewayConfigForm\",\n \"properties\": {\n \"gatewayTitle\": {\n \"title\": \"Gateway form title\",\n \"type\": \"string\",\n \"default\": \"Gateway Config Form\"\n }\n }\n },\n \"form\": [\n \"gatewayTitle\"\n ]\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\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gatewayTitle\":\"Gateway Config Form\"},\"title\":\"Config form\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" + } } ] -} \ No newline at end of file +} diff --git a/ui/package.json b/ui/package.json index ccf18eab93..96c94bf677 100644 --- a/ui/package.json +++ b/ui/package.json @@ -61,6 +61,7 @@ "js-beautify": "^1.10.0", "json-schema-defaults": "^0.2.0", "jstree": "^3.3.8", + "jszip": "^3.2.2", "jstree-bootstrap-theme": "^1.0.1", "leaflet": "^1.5.1", "leaflet-polylinedecorator": "^1.6.0", diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 6657caa8fe..e78af1dff7 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -584,6 +584,32 @@ export default angular.module('thingsboard.types', []) opc: "OPC UA", modbus: "MODBUS" }, + gatewayConfigType: { + mqtt: { + value: "mqtt", + name: "MQTT" + }, + modbus: { + value: "modbus", + name: "Modbus" + }, + opc_ua: { + value: "opcua", + name: "OPC-UA" + }, + ble: { + value: "ble", + name: "BLE" + } + }, + gatewayLogLevel: { + none: "NONE", + critical: "CRITICAL", + error: "ERROR", + warning: "WARNING", + info: "INFO", + debug: "DEBUG" + }, extensionValueType: { string: 'value.string', long: 'value.long', diff --git a/ui/src/app/components/gateWay/gateway-config-dialog.tpl.html b/ui/src/app/components/gateWay/gateway-config-dialog.tpl.html new file mode 100644 index 0000000000..ce55d15f48 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config-dialog.tpl.html @@ -0,0 +1,75 @@ + + + + +
+

+ gateway.title-connectors-json +

+ + + + +
+
+ +
+
+
+ + + + {{'gateway.tidy'|translate}} + {{'gateway.tidy-tip' | translate }} + + +
+
+
+
+
+ +
+
+
+ + + {{'action.save'|translate}} + + + {{'action.cancel'|translate }} + + + +
diff --git a/ui/src/app/components/gateWay/gateway-config-select.directive.js b/ui/src/app/components/gateWay/gateway-config-select.directive.js new file mode 100644 index 0000000000..5178fed6d6 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config-select.directive.js @@ -0,0 +1,137 @@ +/* + * Copyright © 2016-2020 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. + */ +import './gateway-config-select.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayAliasSelectTemplate from './gateway-config-select.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + + +/* eslint-disable angular/angularelement */ + +export default angular.module('thingsboard.directives.gatewayConfigSelect', []) + .directive('tbGatewayConfigSelect', GatewayConfigSelect) + .name; + +/*@ngInject*/ +function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + var template = $templateCache.get(gatewayAliasSelectTemplate); + element.html(template); + + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; + + scope.ngModelCtrl = ngModelCtrl; + scope.singleSelect = null; + + scope.updateValidity = function () { + var value = ngModelCtrl.$viewValue; + var valid = angular.isDefined(value) && value != null || !scope.tbRequired; + ngModelCtrl.$setValidity('singleSelect', valid); + }; + + scope.$watch('singleSelect', function () { + scope.updateView(); + }); + + scope.gatewayNameSearch = function (gatewaySearchText) { + return gatewaySearchText ? scope.gatewayList.filter( + scope.createFilterForGatewayName(gatewaySearchText)) : scope.gatewayList; + }; + + scope.createFilterForGatewayName = function (query) { + var lowercaseQuery = query.toLowerCase(); + return function filterFn(device) { + return (device.toLowerCase().indexOf(lowercaseQuery) === 0); + }; + }; + + scope.updateView = function () { + ngModelCtrl.$setViewValue(scope.singleSelect); + scope.updateValidity(); + let deviceObj = {"name": scope.singleSelect, "type": "Gateway", "additionalInfo": { + "gateway": true + }}; + scope.getAccessToken(deviceObj); + }; + + ngModelCtrl.$render = function () { + if (ngModelCtrl.$viewValue) { + scope.singleSelect = ngModelCtrl.$viewValue; + } + }; + + scope.textIsEmpty = function (str) { + return (!str || 0 === str.length); + }; + + scope.gatewayNameEnter = function ($event) { + if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { + $event.preventDefault(); + let indexRes = scope.gatewayList.findIndex((element) => element.key === scope.gatewaySearchText); + if (indexRes === -1) { + scope.createNewGatewayDialog($event, {name: scope.gatewaySearchText}); + } + } + }; + + scope.createNewGatewayDialog = function ($event, deviceName) { + if ($event) { + $event.stopPropagation(); + } + var title = $translate.instant('gateway.create-new-gateway'); + var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName.name}); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then( + () => { + let deviceObj = {"name": deviceName.name, "type": "Gateway", "additionalInfo": { + "gateway": true + }}; + scope.createDevice(deviceObj); + }, + () => { + scope.gatewaySearchText = ""; + } + ); + }; + $compile(element.contents())(scope); + }; + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: { + tbRequired: '=?', + allowedEntityTypes: '=?', + gatewayList: '=?', + getAccessToken: '=', + createDevice: '=', + theForm: '=' + } + }; +} + +/* eslint-enable angular/angularelement */ diff --git a/ui/src/app/components/gateWay/gateway-config-select.scss b/ui/src/app/components/gateWay/gateway-config-select.scss new file mode 100644 index 0000000000..1c189a7279 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config-select.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2020 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-gateway-autocomplete { + .tb-not-found { + line-height: 1.5; + white-space: normal; + + .tb-no-gateway { + line-height: 48px; + } + } +} + +.tb-gateway-autocomplete-container.md-virtual-repeat-container.md-autocomplete-suggestions-container{ + z-index: 70; +} + +md-autocomplete{ + md-input-container{ + margin-bottom: 0; + } +} diff --git a/ui/src/app/components/gateWay/gateway-config-select.tpl.html b/ui/src/app/components/gateWay/gateway-config-select.tpl.html new file mode 100644 index 0000000000..57c89e5e4a --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config-select.tpl.html @@ -0,0 +1,54 @@ + +
+ + + {{item}} + + +
+
+ gateway.no-gateway-found +
+
+ gateway.no-gateway-matching + gateway.create-new-gateway +
+
+
+
+
Test
+
+
+
diff --git a/ui/src/app/components/gateWay/gateway-config.directive.js b/ui/src/app/components/gateWay/gateway-config.directive.js new file mode 100644 index 0000000000..66ba36620c --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config.directive.js @@ -0,0 +1,317 @@ +/* + * Copyright © 2016-2020 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. + */ +import './gateway-config.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayTemplate from './gateway-config.tpl.html'; +import gatewayDialogTemplate from './gateway-config-dialog.tpl.html'; +import beautify from "js-beautify"; + +/* eslint-enable import/no-unresolved, import/default */ +const js_beautify = beautify.js; + +export default angular.module('thingsboard.directives.gatewayConfig', []) + .directive('tbGatewayConfig', GatewayConfig) + .name; + +/*@ngInject*/ +function GatewayConfig() { + return { + restrict: "E", + scope: true, + bindToController: { + disabled: '=ngDisabled', + titleText: '@?', + keyPlaceholderText: '@?', + valuePlaceholderText: '@?', + noDataText: '@?', + gatewayConfig: '=', + changeAlignment: '=' + }, + controller: GatewayConfigController, + controllerAs: 'vm', + templateUrl: gatewayTemplate + }; +} + +/*@ngInject*/ +function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types, toast, $timeout, $compile, $translate) { //eslint-disable-line + + let vm = this; + + vm.kvList = []; + vm.types = types; + $scope.$watch('vm.gatewayConfig', () => { + vm.stopWatchKvList(); + vm.kvList.length = 0; + if (vm.gatewayConfig) { + for (var property in vm.gatewayConfig) { + if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) { + vm.kvList.push( + { + enabled: vm.gatewayConfig[property].enabled, + key: property + '', + value: vm.gatewayConfig[property].connector + '', + config: js_beautify(vm.gatewayConfig[property].config + '', {indent_size: 4}) + } + ); + } + } + } + $mdUtil.nextTick(() => { + vm.watchKvList(); + }); + }); + + vm.watchKvList = () => { + $scope.kvListWatcher = $scope.$watch('vm.kvList', () => { + if (!vm.gatewayConfig) { + return; + } + for (let property in vm.gatewayConfig) { + if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) { + delete vm.gatewayConfig[property]; + } + } + for (let i = 0; i < vm.kvList.length; i++) { + let entry = vm.kvList[i]; + if (entry.key && entry.value) { + let connectorJSON = angular.toJson({ + enabled: entry.enabled, + connector: entry.value, + config: angular.fromJson(entry.config) + }); + vm.gatewayConfig [entry.key] = angular.fromJson(connectorJSON); + } + } + }, true); + }; + + vm.stopWatchKvList = () => { + if ($scope.kvListWatcher) { + $scope.kvListWatcher(); + $scope.kvListWatcher = null; + } + }; + + vm.removeKeyVal = (index) => { + if (index > -1) { + vm.kvList.splice(index, 1); + } + }; + + vm.addKeyVal = () => { + if (!vm.kvList) { + vm.kvList = []; + } + vm.kvList.push( + { + enabled: false, + key: '', + value: '', + config: '{}' + } + ); + } + + vm.openConfigDialog = ($event, index, config, typeName) => { + if ($event) { + $event.stopPropagation(); + } + $mdDialog.show({ + controller: GatewayDialogController, + controllerAs: 'vm', + templateUrl: gatewayDialogTemplate, + parent: angular.element($document[0].body), + locals: { + config: config, + typeName: typeName + }, + targetEvent: $event, + fullscreen: true, + multiple: true, + }).then(function (config) { + if (config) { + if (index > -1) { + vm.kvList[index].config = config; + } + } + }, function () { + }); + + }; + + vm.configTypeChange = (keyVal) => { + for (let prop in types.gatewayConfigType) { + if (types.gatewayConfigType[prop].value === keyVal.value) { + if (!keyVal.key) { + keyVal.key = vm.configTypeChangeValid(types.gatewayConfigType[prop].name, 0); + } + } + } + vm.checkboxValid(keyVal); + }; + + vm.keyValChange = (keyVal, indexKey) => { + keyVal.key = vm.keyValChangeValid(keyVal.key, 0, indexKey); + vm.checkboxValid(keyVal); + }; + + vm.configTypeChangeValid = (name, index) => { + let newKeyName = index ? name + index : name; + let indexRes = vm.kvList.findIndex((element) => element.key === newKeyName); + return indexRes === -1 ? newKeyName : vm.configTypeChangeValid(name, ++index); + }; + + vm.keyValChangeValid = (name, index, indexKey) => { + angular.forEach(vm.kvList, function (value, key) { + let nameEq = (index === 0) ? name : name + index; + if (key !== indexKey && value.key && value.key === nameEq) { + index++; + vm.keyValChangeValid(name, index, indexKey); + } + + }); + return (index === 0) ? name : name + index; + }; + + vm.buttonValid = (config) => { + return (angular.equals("{}", config)) ? "md-warn" : "md-primary"; + }; + + vm.checkboxValid = (keyVal) => { + if (!keyVal.key || angular.equals("", keyVal.key) + || !keyVal.value || angular.equals("", keyVal.value) + || angular.equals("{}", keyVal.config)) { + return keyVal.enabled = false; + } + return true; + }; + vm.checkboxValidMouseover = ($event, keyVal) => { + console.log($event, keyVal); //eslint-disable-line + vm.checkboxValidClick ($event, keyVal); + }; + + vm.checkboxValidClick = ($event, keyVal) => { + if (!vm.checkboxValid(keyVal)) { + let errTxt = ""; + if (!keyVal.key || angular.equals("", keyVal.key)) { + errTxt = $translate.instant('gateway.keyval-name-err'); + } + + if (!keyVal.value || angular.equals("", keyVal.value)) { + errTxt += '
' + $translate.instant('gateway.keyval-type-err') + '
'; + } + + if (angular.equals("{}", keyVal.config)) { + errTxt += '
' + $translate.instant('gateway.keyval-config-err') + '
'; + } + if (!angular.equals("", errTxt)) { + displayTooltip($event, '
' + + '
' + + '
' + $translate.instant('gateway.keyval-save-err') + '
' + + '
' + errTxt + '
' + + '
' + + '
'); + } + } + else { + destroyTooltips(); + } + }; + + + function displayTooltip(event, content) { + destroyTooltips(); + vm.tooltipTimeout = $timeout(() => { + var element = angular.element(event.target); + element.tooltipster( + { + theme: 'tooltipster-shadow', + delay: 10, + animation: 'grow', + side: 'right' + } + ); + var contentElement = angular.element(content); + $compile(contentElement)($scope); + var tooltip = element.tooltipster('instance'); + tooltip.content(contentElement); + tooltip.open(); + }, 500); + } + + function destroyTooltips() { + if (vm.tooltipTimeout) { + $timeout.cancel(vm.tooltipTimeout); + vm.tooltipTimeout = null; + } + var instances = angular.element.tooltipster.instances(); + instances.forEach((instance) => { + if (!instance.isErrorTooltip) { + instance.destroy(); + } + }); + } +} + +/*@ngInject*/ +function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) { + let vm = this; + vm.doc = $document[0]; + vm.config = angular.copy(config); + vm.typeName = "" + typeName; + vm.configAreaOptions = { + useWrapMode: false, + mode: 'json', + showGutter: true, + showPrintMargin: true, + theme: 'github', + advanced: { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }, + onLoad: function (_ace) { + _ace.$blockScrolling = 1; + } + }; + + vm.validateConfig = (model, editorName) => { + if (model && model.length) { + try { + angular.fromJson(model); + $scope.theForm[editorName].$setValidity('configJSON', true); + } catch (e) { + $scope.theForm[editorName].$setValidity('configJSON', false); + } + } + }; + + vm.save = () => { + $mdDialog.hide(vm.config); + }; + + vm.cancel = () => { + $mdDialog.hide(); + }; + + vm.beautifyJson = () => { + vm.config = js_beautify(vm.config, {indent_size: 4}); + }; +} + diff --git a/ui/src/app/components/gateWay/gateway-config.scss b/ui/src/app/components/gateWay/gateway-config.scss new file mode 100644 index 0000000000..f128db8f21 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2020 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. + */ +.gateway-config { + span.no-data-found { + position: relative; + display: flex; + height: 40px; + text-transform: uppercase; + + &.disabled { + color: rgba(0, 0, 0, .38); + } + } + + .gateway-config-row{ + md-input-container{ + margin-bottom: 0; + } + + &.gateway-config-row-vertical { + flex-direction: column; + } + } + + .action-buttons.gateway-config-row-vertical { + flex-direction: column; + justify-content: space-evenly; + } +} + +.gateway-config-dialog{ + .md-button.tidy{ + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0 5px 0 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + } + + .tb-json-toolbar{ + height: 40px; + } + + .tb-json-panel { + height: calc(100% - 80px); + margin-left: 15px; + border: 1px solid #c0c0c0; + + .tb-json-input { + width: 100%; + min-width: 400px; + height: 100%; + + &:not(.fill-height) { + min-height: 200px; + } + } + } +} + +@media (max-width: 425px){ + .gateway-config-dialog{ + .tb-json-panel { + .tb-json-input { + min-width: 200px; + } + } + } +} diff --git a/ui/src/app/components/gateWay/gateway-config.tpl.html b/ui/src/app/components/gateWay/gateway-config.tpl.html new file mode 100644 index 0000000000..565c771099 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-config.tpl.html @@ -0,0 +1,94 @@ + +
+
+
+ + + + + {{ 'gateway.enabled' | translate }} + + +
+
+ + + + + {{configType.value}} + + + + {{ 'gateway.connector-type' | translate }} + + + + +
+
extension.field-required
+
+ + {{ 'gateway.name' | translate }} + +
+
+
+ + settings_ethernet + + {{ 'gateway.update-config' | translate }} + + + + close + + {{ 'gateway.delete' | translate }} + + +
+
+ {{vm.noDataText ? vm.noDataText : 'gateway.no-connectors'}} +
+ + + {{ 'gateway.add-connectors' | translate }} + + action.add + +
+
diff --git a/ui/src/app/components/gateWay/gateway-form.directive.js b/ui/src/app/components/gateWay/gateway-form.directive.js new file mode 100644 index 0000000000..2aa05ce004 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-form.directive.js @@ -0,0 +1,498 @@ +/* + * Copyright © 2016-2020 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. + */ +import './gateway-form.scss'; +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayFormTemplate from './gateway-form.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.directives.gatewayForm', []) + .directive('tbGatewayForm', GatewayForm) + .name; + +/*@ngInject*/ +function GatewayForm() { + return { + restrict: "E", + scope: true, + bindToController: { + disabled: '=ngDisabled', + keyPlaceholderText: '@?', + valuePlaceholderText: '@?', + noDataText: '@?', + formId: '=', + ctx: '=', + gatewayFormConfig: '=', + theForm: '=' + }, + controller: GatewayFormController, + controllerAs: 'vm', + templateUrl: gatewayFormTemplate + }; +} + +/*@ngInject*/ +function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q) { + $scope.$mdExpansionPanel = $mdExpansionPanel; + let vm = this; + const attributeNameClinet = "current_configuration"; + const attributeNameServer = "configuration_drafts"; + const attributeNameShared = "configuration"; + const attributeNameLogShared = "RemoteLoggingLevel"; + vm.remoteLoggingConfig = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"'; + vm.types = types; + + vm.configurations = { + singleSelect: '', + host: $document[0].domain, + port: 1883, + remoteConfiguration: true, + accessToken: '', + entityType: '', + entityId: '', + storageType: "memoryStorage", // "memoryStorage"; fileStorage + readRecordsCount: 100, + maxRecordsCount: 10000, + dataFolderPath: './data/', + maxFilesCount: 5, + securityType: "accessToken", // "accessToken", "tls" + caCertPath: '/etc/thingsboard-gateway/ca.pem', + privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem', + certPath: '/etc/thingsboard-gateway/certificate.pem', + connectors: {}, + remoteLoggingLevel: "DEBUG", // level login + remoteLoggingPathToLogs: './logs/' + }; + getGatewaysListByUser(true); + + vm.securityTypes = [{ + name: 'Access Token', + value: 'accessToken' + }, { + name: 'TLS', + value: 'tls' + }]; + + vm.storageTypes = [{ + name: 'Memory storage', + value: 'memoryStorage' + }, { + name: 'File storage', + value: 'fileStorage' + }]; + + $scope.$on('gateway-form-resize', function (event, formId) { + if (vm.formId == formId) { + updateWidgetDisplaying(); + } + }); + + function updateWidgetDisplaying() { + if (vm.ctx && vm.ctx.$container) { + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425); + } + } + + updateWidgetDisplaying(); + + vm.getAccessToken = (deviceObj) => { + if (deviceObj.name) { + deviceService.findByName(deviceObj.name, {ignoreErrors: true}) + .then( + function (device) { + getDeviceCredential(device.id.id); + } + ) + } + }; + + function getDeviceCredential(deviceId) { + return deviceService.getDeviceCredentials(deviceId).then( + (deviceCredentials) => { + vm.configurations.accessToken = deviceCredentials.credentialsId; + vm.configurations.entityType = deviceCredentials.deviceId.entityType; + vm.configurations.entityId = deviceCredentials.deviceId.id; + vm.getAttributeStart(); + } + ); + } + + vm.createDevice = (deviceObj) => { + deviceService.findByName(deviceObj.name, {ignoreErrors: true}) + .then( + function (device) { + getDeviceCredential(device.id.id).then(() => { + getGatewaysListByUser(); + }); + }, + function () { + deviceService.saveDevice(deviceObj).then( + (device) => { + deviceService.getDeviceCredentials(device.id.id).then( + (data) => { + vm.configurations.accessToken = data.credentialsId; + vm.configurations.entityType = device.id.entityType; + vm.configurations.entityId = device.id.id; + vm.getAttributeStart(); + getGatewaysListByUser(); + } + ); + } + ); + }); + }; + + vm.saveAttributeConfig = () => { + vm.setAttribute(attributeNameShared, $window.btoa(angular.toJson(vm.getConfigAllByAttributeJSON())), types.attributesScope.shared.value); + vm.setAttribute(attributeNameServer, $window.btoa(angular.toJson(vm.getConfigByAttributeTmpJSON())), types.attributesScope.server.value); + vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); + }; + + vm.getAttributeStart = () => { + let initResps = []; + vm.configurations.connectors = {}; + initResps.push(vm.getAttributeConfig(attributeNameClinet, types.attributesScope.client.value)); + initResps.push(vm.getAttributeConfig(attributeNameServer, types.attributesScope.server.value)); + initResps.push(vm.getAttributeConfig(attributeNameLogShared, types.attributesScope.shared.value)); + $q.all(initResps).then((resp) => { + vm.getAttributeInitFromClient(resp[0]); + vm.getAttributeInitFromServer(resp[1]); + vm.getAttributeInitFromShared(resp[2]); + }, (err) => { + console.log("getAttribute_error", err); //eslint-disable-line + }); + }; + + vm.getAttributeConfig = (attributeName, typeValue) => { + let keys = [attributeName]; + return attributeService.getEntityAttributesValues(vm.configurations.entityType, vm.configurations.entityId, typeValue, keys); + }; + + vm.setAttribute = (attributeName, attributeConfig, typeValue) => { + let attributes = [ + { + key: attributeName, + value: attributeConfig + } + ]; + attributeService.saveEntityAttributes(vm.configurations.entityType, vm.configurations.entityId, typeValue, attributes).then(() => { + }, (err) => { + console.log("setAttribute_", err); //eslint-disable-line + }); + }; + + vm.exportConfig = () => { + let fileZip = {}; + fileZip["tb_gateway.yaml"] = vm.getConfig(); + vm.createConfigByExport(fileZip); + vm.getLogsConfigByExport(fileZip); + importExport.exportJSZip(fileZip, 'config'); + vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); + }; + + vm.getConfig = () => { + let config; + config = 'thingsboard:\n'; + config += ' host: ' + vm.configurations.host + '\n'; + config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n'; + config += ' port: ' + vm.configurations.port + '\n'; + config += ' security:\n'; + if (vm.configurations.securityType === 'accessToken') { + config += ' access-token: ' + vm.configurations.accessToken + '\n'; + } else if (vm.configurations.securityType === 'tls') { + config += ' ca_cert: ' + vm.configurations.caCertPath + '\n'; + config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n'; + config += ' cert: ' + vm.configurations.certPath + '\n'; + } + config += 'storage:\n'; + if (vm.configurations.storageType === 'memoryStorage') { + config += ' type: memory\n'; + config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n'; + } else if (vm.configurations.storageType === 'fileStorage') { + config += ' type: file\n'; + config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n'; + config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n'; + config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n'; + } + config += 'connectors:\n'; + for (let connector in vm.configurations.connectors) { + if (vm.configurations.connectors[connector].enabled) { + config += ' -\n'; + config += ' name: ' + connector + ' Connector\n'; + config += ' type: ' + vm.configurations.connectors[connector].connector + '\n'; + config += ' configuration: ' + vm.validFileName(connector) + ".json" + '\n'; + } + } + return config; + }; + + vm.createConfigByExport = (fileZipAdd) => { + for (let connector in vm.configurations.connectors) { + if (vm.configurations.connectors[connector].enabled) { + fileZipAdd[vm.validFileName(connector) + ".json"] = angular.toJson(vm.configurations.connectors[connector].config); + } + } + }; + + vm.getLogsConfigByExport = (fileZipAdd) => { + fileZipAdd["logs.conf"] = vm.getLogsConfig(); + }; + + vm.getLogsConfig = () => { + return vm.remoteLoggingConfig + .replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel) + .replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs); + }; + + vm.getConfigAllByAttributeJSON = () => { + let thingsBoardAll = {}; + thingsBoardAll["thingsboard"] = vm.getConfigMainByAttributeJSON(); + vm.getConfigByAttributeJSON(thingsBoardAll); + return thingsBoardAll; + }; + + vm.getConfigMainByAttributeJSON = () => { + let configMain = {}; + let thingsBoard = {}; + thingsBoard.host = vm.configurations.host; + thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration; + thingsBoard.port = vm.configurations.port; + let security = {}; + if (vm.configurations.securityType === 'accessToken') { + security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : "" + } else { + security.caCert = vm.configurations.caCertPath; + security.privateKey = vm.configurations.privateKeyPath; + security.cert = vm.configurations.certPath; + } + thingsBoard.security = security; + configMain.thingsboard = thingsBoard; + + let storage = {}; + if (vm.configurations.storageType === 'memoryStorage') { + storage.type = "memory"; + storage.read_records_count = vm.configurations.readRecordsCount; + storage.max_records_count = vm.configurations.maxRecordsCount; + } else if (vm.configurations.storageType === 'fileStorage') { + storage.type = "file"; + storage.data_folder_path = vm.configurations.dataFolderPath; + storage.max_file_count = vm.configurations.maxFilesCount; + storage.max_read_records_count = vm.configurations.readRecordsCount; + storage.max_records_per_file = vm.configurations.maxRecordsCount; + } + configMain.storage = storage; + + let conn = []; + for (let connector in vm.configurations.connectors) { + if (vm.configurations.connectors[connector].enabled) { + let connect = {}; + connect.configuration = vm.validFileName(connector) + ".json"; + connect.name = connector; + connect.type = vm.configurations.connectors[connector].connector; + conn.push(connect); + } + } + configMain.connectors = conn; + + configMain.logs = $window.btoa(vm.getLogsConfig()); + + return configMain; + }; + + vm.getConfigByAttributeJSON = (thingsBoardBy) => { + for (let connector in vm.configurations.connectors) { + if (vm.configurations.connectors[connector].enabled) { + let typeAr = vm.configurations.connectors[connector].connector; + let objTypeAll = []; + for (let conn in vm.configurations.connectors) { + if (typeAr === vm.configurations.connectors[conn].connector && vm.configurations.connectors[conn].enabled) { + let objType = {}; + objType["name"] = conn; + objType["config"] = vm.configurations.connectors[conn].config; + objTypeAll.push(objType); + } + } + if (objTypeAll.length > 0) { + thingsBoardBy[typeAr] = objTypeAll; + } + } + } + }; + + vm.getConfigByAttributeTmpJSON = () => { + let connects = {}; + for (let connector in vm.configurations.connectors) { + if (!vm.configurations.connectors[connector].enabled && Object.keys(vm.configurations.connectors[connector].config).length !== 0) { + let conn = {}; + conn["connector"] = vm.configurations.connectors[connector].connector; + conn["config"] = vm.configurations.connectors[connector].config; + connects[connector] = conn; + } + } + return connects; + }; + + function getGatewaysListByUser(firstInit) { + vm.gateways = []; + vm.currentUser = userService.getCurrentUser(); + if (vm.currentUser.authority === 'TENANT_ADMIN') { + deviceService.getTenantDevices({limit: 500}).then( + (devices) => { + if (devices.data.length > 0) { + devices.data.forEach((device) => { + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { + vm.gateways.push(device.name); + if (firstInit && vm.gateways.length && device.name === vm.gateways[0]) { + vm.configurations.singleSelect = vm.gateways[0]; + let deviceObj = { + "name": vm.configurations.singleSelect, + "type": "Gateway", + "additionalInfo": { + "gateway": true + } + }; + vm.getAccessToken(deviceObj); + } + } + }); + } + } + ); + } else if (vm.currentUser.authority === 'CUSTOMER_USER') { + deviceService.getCustomerDevices(vm.currentUser.customerId, {limit: 500}).then( + (devices) => { + if (devices.data.length > 0) { + devices.data.forEach((device) => { + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { + vm.gateways.push(device.name); + if (firstInit && vm.gateways.length) { + vm.configurations.singleSelect = vm.gateways[0]; + let deviceObj = { + "name": vm.configurations.singleSelect, + "type": "Gateway", + "additionalInfo": { + "gateway": true + } + }; + vm.getAccessToken(deviceObj); + } + } + }); + } + } + ); + } + } + + vm.getAttributeInitFromClient = (resp) => { + if (resp.length > 0) { + vm.configurations.connectors = {}; + let attribute = angular.fromJson($window.atob(resp[0].value)); + for (var type in attribute) { + let keyVal = attribute[type]; + if (type === "thingsboard") { + if (keyVal !== null && Object.keys(keyVal).length > 0) { + vm.setConfigMain(keyVal); + } + } else { + for (let typeVal in keyVal) { + let typeName = ''; + if (Object.prototype.hasOwnProperty.call(keyVal[typeVal], 'name')) { + typeName = 'name'; + } + let key = ""; + key = (typeName === "") ? "No name" : ((typeName === 'name') ? keyVal[typeVal].name : keyVal[typeVal][typeName].name); + let conn = {}; + conn["enabled"] = true; + conn["connector"] = type; + conn["config"] = angular.toJson(keyVal[typeVal].config); + vm.configurations.connectors[key] = conn; + } + } + } + } + }; + + vm.getAttributeInitFromServer = (resp) => { + if (resp.length > 0) { + let attribute = angular.fromJson($window.atob(resp[0].value)); + for (let key in attribute) { + let conn = {}; + conn["enabled"] = false; + conn["connector"] = attribute[key].connector; + conn["config"] = angular.toJson(attribute[key].config); + vm.configurations.connectors[key] = conn; + } + } + }; + + vm.getAttributeInitFromShared = (resp) => { + if (resp.length > 0) { + if (vm.types.gatewayLogLevel[resp[0].value.toLowerCase()]) { + vm.configurations.remoteLoggingLevel = resp[0].value.toUpperCase(); + } + } else { + vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug; + } + }; + + vm.setConfigMain = (keyVal) => { + if (Object.prototype.hasOwnProperty.call(keyVal, 'thingsboard')) { + vm.configurations.host = keyVal.thingsboard.host; + vm.configurations.port = keyVal.thingsboard.port; + vm.configurations.remoteConfiguration = keyVal.thingsboard.remoteConfiguration; + if (Object.prototype.hasOwnProperty.call(keyVal.thingsboard.security, 'accessToken')) { + vm.configurations.securityType = 'accessToken'; + vm.configurations.accessToken = keyVal.thingsboard.security.accessToken; + } else { + vm.configurations.securityType = 'tls'; + vm.configurations.caCertPath = keyVal.thingsboard.security.caCert; + vm.configurations.privateKeyPath = keyVal.thingsboard.security.private_key; + vm.configurations.certPath = keyVal.thingsboard.security.cert; + } + } + if (Object.prototype.hasOwnProperty.call(keyVal, 'storage') && Object.prototype.hasOwnProperty.call(keyVal.storage, 'type')) { + if (keyVal.storage.type === 'memory') { + vm.configurations.storageType = 'memoryStorage'; + vm.configurations.readRecordsCount = keyVal.storage.read_records_count; + vm.configurations.maxRecordsCount = keyVal.storage.max_records_count; + } else if (keyVal.storage.type === 'file') { + vm.configurations.storageType = 'fileStorage'; + vm.configurations.dataFolderPath = keyVal.storage.data_folder_path; + vm.configurations.maxFilesCount = keyVal.storage.max_file_count; + vm.configurations.readRecordsCount = keyVal.storage.read_records_count; + vm.configurations.maxRecordsCount = keyVal.storage.max_records_count; + } + } + }; + + vm.setSaveTypeConfig = (itemVal) => { + vm.configurations.remoteConfiguration = itemVal.item; + }; + + vm.validFileName = (fileName) => { + let fileName1 = fileName.replace("_", ""); + let fileName2 = fileName1.replace("-", ""); + let fileName3 = fileName2.replace(/^\s+|\s+$/g, ''); + let fileName4 = fileName3.toLowerCase(); + return fileName4; + }; +} + + diff --git a/ui/src/app/components/gateWay/gateway-form.scss b/ui/src/app/components/gateWay/gateway-form.scss new file mode 100644 index 0000000000..a5e7bc8b44 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-form.scss @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2020 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. + */ +.gateway-form{ + padding: 5px 5px 0; + + .gateway-form-row{ + md-input-container{ + margin-bottom: 0; + } + + &.gateway-config-row-vertical{ + flex-direction: column; + + .md-select-container{ + margin-bottom: 14px; + } + } + } + + .form-action-buttons{ + padding-top: 8px; + } +} + +.security-type { + margin-top: 38px; +} diff --git a/ui/src/app/components/gateWay/gateway-form.tpl.html b/ui/src/app/components/gateWay/gateway-form.tpl.html new file mode 100644 index 0000000000..8ea0cfcc07 --- /dev/null +++ b/ui/src/app/components/gateWay/gateway-form.tpl.html @@ -0,0 +1,219 @@ + +
+ + + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + + + + + + + {{securityType.name}} + + + +
+ + + +
+
extension.field-required
+
+
+ + + +
+
extension.field-required
+
max
+
min
+
+
+
+
+ + + + + + + + + + + + +
+ + {{ 'gateway.remote' | translate }} + {{'gateway.remote-tip' | translate }} + +
+ + + + + {{loggingLevel}} + + + + + + +
+
extension.field-required
+
+
+
+
+
+
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + + + + + {{storageType.name}} + + + + +
+ + + +
+
extension.field-required
+
+
+ + + + +
+
extension.field-required
+
+
+
+ +
+ + + +
+
extension.field-required
+
+
+ + + + +
+
extension.field-required
+
+
+
+
+
+
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + + + +
+
+
+
+ + {{'action.download' | translate }} + {{'gateway.download-tip' | translate }} + + + + {{'action.save' | translate }} + {{'gateway.save-tip' | translate }} + +
+
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index cdc9b985b8..6220fc3c6a 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -18,6 +18,7 @@ import importDialogTemplate from './import-dialog.tpl.html'; import importDialogCSVTemplate from './import-dialog-csv.tpl.html'; import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; +import * as JSZip from 'jszip'; /* eslint-enable import/no-unresolved, import/default */ @@ -28,6 +29,10 @@ import entityAliasesTemplate from '../entity/alias/entity-aliases.tpl.html'; export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope, dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) { + const JSZIP_TYPE = { + mimeType: 'application/zip', + extension: 'zip' + }; var service = { exportDashboard: exportDashboard, @@ -40,6 +45,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, importWidgetType: importWidgetType, exportWidgetsBundle: exportWidgetsBundle, importWidgetsBundle: importWidgetsBundle, + exportJSZip: exportJSZip, exportExtension: exportExtension, importExtension: importExtension, importEntities: importEntities, @@ -851,7 +857,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, }); return $q.all(promises); } - + function createMultiEntity(arrayData, entityType, updateData, config) { let partSize = 100; partSize = arrayData.length > partSize ? partSize : arrayData.length; @@ -982,6 +988,49 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, let dialogElement = element[0].getElementsByTagName('md-dialog'); dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px"; } + + /** + * + * @param data + * @param filename + * Warn data !!! Not object, if object, then object convert from object to format txt + * Example: data = {keyNameFile1: valueFile1, + * keyNameFile2: valueFile2...} + * fileName - name file of the arhiv + */ + function exportJSZip(data, filename) { + let jsZip = new JSZip(); + for (let keyName in data) { + let valueData = data[keyName]; + jsZip.file(keyName, valueData); + } + jsZip.generateAsync({type: "Blob"}).then(function (content) { + downloadFile(content, filename, JSZIP_TYPE); + }); + } + + + function downloadFile(data, filename, fileType) { + console.log("downloadFile", data, filename, fileType); // eslint-disable-line + if (!filename) { + filename = 'download'; + } + filename += '.' + fileType.extension; + var blob = new Blob([data], {type: fileType.mimeType}); + // FOR IE: + if (window.navigator && window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(blob, filename); + } else { + var e = document.createEvent('MouseEvents'), + a = document.createElement('a'); + a.download = filename; + a.href = window.URL.createObjectURL(blob); + a.dataset.downloadurl = [fileType.mimeType, a.download, a.href].join(':'); + e.initEvent('click', true, false, window, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + a.dispatchEvent(e); + } + } } /* eslint-enable no-undef, angular/window-service, angular/document-service */ diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index 84f89adb1b..d674a1fdd3 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -30,6 +30,9 @@ import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardNavTree from '../components/nav-tree.directive'; import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; import thingsboardKvMap from '../components/kv-map.directive'; +import thingsboardGatewayConfig from '../components/gateWay/gateway-config.directive'; +import thingsboardGatewayConfigSelect from '../components/gateWay/gateway-config-select.directive'; +import thingsboardGatewayForm from '../components/gateWay/gateway-form.directive'; import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; import thingsboardJsonContent from '../components/json-content.directive'; @@ -93,6 +96,9 @@ export default angular.module('thingsboard.home', [ thingsboardNavTree, thingsboardDashboardAutocomplete, thingsboardKvMap, + thingsboardGatewayConfig, + thingsboardGatewayConfigSelect, + thingsboardGatewayForm, thingsboardJsonObjectEdit, thingsboardJsonContent ]) diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 37caf9875b..5acd0f81e5 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -50,7 +50,8 @@ "export": "Export", "share-via": "Share via {{provider}}", "continue": "Continue", - "discard-changes": "Discard Changes" + "discard-changes": "Discard Changes", + "download": "Download" }, "aggregation": { "aggregation": "Aggregation", @@ -1124,6 +1125,58 @@ "function": { "function": "Function" }, + "gateway": { + "key": "Key configuration", + "value": "Value configuration", + "remove-entry": "Remove configuration", + "add-entry": "Add configuration", + "no-data": "No configurations", + "gateway-required": "Gateway is required.", + "gateway-name": "Gateway name", + "create-new-gateway": "Create a new gateway", + "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?", + "no-gateway-matching": " '{{item}}' not found.", + "thingsboard": "ThingsBoard", + "connectors": "Connectors configuration", + "thingsboard-host": "ThingsBoard Host", + "thingsboard-port": "ThingsBoard Port", + "security-type": "Security type", + "tls-path-ca-certificate": "Path to CA certificate on gateway:", + "tls-path-private-key": "Path to private key on gateway:", + "tls-path-client-certificate": "Path to client certificate on gateway:", + "storage": "Storage", + "storage-type": "Storage type", + "storage-read-time": "Read records per time:", + "storage-max-time": "Maximum records per time:", + "storage-max-files": "Maximum files:", + "storage-data-path": "Data folder path:", + "download-tip": "Download configuration file", + "save-tip": "Save configuration file", + "remote-tip": "Allow remote configuration", + "remote": "Remote configuration", + "remote-logging-level": "Logging level", + "remote-logging-path-logs": "Path to logs", + "connector-type": "Connector type", + "update-config": "Add/update config JSON", + "delete": "Delete configuration", + "title-connectors-json": "Connector {{typeName}} configuration", + "json-required": "Config json is required for gateway config.", + "json-parse": "Unable to parse config json for gateway config.", + "tidy": "Tidy", + "tidy-tip": "Tidy config JSON", + "transformer-json-config": "JSON for the config*", + "toggle-fullscreen": "Toggle fullscreen", + "add-connectors": "Add new connectors", + "no-connectors": "No connectors", + "enabled": "Enabled", + "name": "Name", + "no-gateway-found": "No gateway found.", + "gateway": "Gateway", + "keyval-save-err": "Save config error", + "keyval-name-err": "Please add Name", + "keyval-type-err": "Please add Connector type", + "keyval-config-err": "Please add configuration JSON" + }, "grid": { "delete-item-title": "Are you sure you want to delete this item?", "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", From 41cbdd154e4a5b7e4387387b76d1aa043cdc4099 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Thu, 30 Jan 2020 12:55:05 +0200 Subject: [PATCH 180/261] fixed the partion date extracting --- .../thingsboard/server/dao/util/PsqlTsAnyDao.java | 2 +- .../server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 12 ++++++++---- .../server/dao/timeseries/PsqlPartition.java | 7 ++----- .../server/dao/timeseries/SqlTsPartitionDate.java | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java index b795ce451c..f2a8800032 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/PsqlTsAnyDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.util; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -@ConditionalOnExpression("('${database.ts.type}'=='sql' || '${database.entities.type}'=='timescale') " + +@ConditionalOnExpression("('${database.ts.type}'=='sql' || '${database.ts.type}'=='timescale') " + "&& '${spring.jpa.database-platform}'=='org.hibernate.dialect.PostgreSQLDialect'") public @interface PsqlTsAnyDao { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index fbaa2f9acf..47848f0af3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -46,6 +46,8 @@ import org.thingsboard.server.dao.util.SqlTsDao; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -298,13 +300,15 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao Date: Thu, 30 Jan 2020 15:14:11 +0200 Subject: [PATCH 181/261] Performance Improvement --- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 62 +++++++++++-------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 47848f0af3..ccf2490fdc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -48,10 +48,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -67,7 +64,7 @@ import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_STA public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao implements TimeseriesDao { private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); - private final Set partitions = ConcurrentHashMap.newKeySet(); + private final Map partitions = new ConcurrentHashMap<>(); private static final ReentrantLock tsCreationLock = new ReentrantLock(); private static final ReentrantLock partitionCreationLock = new ReentrantLock(); @@ -82,6 +79,7 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao partition = SqlTsPartitionDate.parse(partitioning); if (partition.isPresent()) { tsFormat = partition.get(); + if (tsFormat.equals(SqlTsPartitionDate.INDEFINITE)) { + indefinitePartition = new PsqlPartition(toMills(EPOCH_START), Long.MAX_VALUE, tsFormat.getPattern()); + savePartition(indefinitePartition); + } } else { log.warn("Incorrect configuration of partitioning {}", partitioning); throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); @@ -116,23 +118,22 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> { - String strKey = query.getKey(); - Integer keyId = getOrSaveKeyId(strKey); - tsKvRepository.delete( - entityId.getId(), - keyId, - query.getStartTs(), - query.getEndTs()); - return null; - }); + return service.submit(() -> { + String strKey = query.getKey(); + Integer keyId = getOrSaveKeyId(strKey); + tsKvRepository.delete( + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs()); + return null; + }); } @Override @@ -286,13 +287,13 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao Date: Fri, 31 Jan 2020 15:36:24 +0200 Subject: [PATCH 182/261] Fixed Shutdown Sequence --- .../server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java | 2 +- .../server/dao/sqlts/timescale/TimescaleTimeseriesDao.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java index a26eccbf06..3be9551549 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSimpleSqlTimeseriesDao.java @@ -70,7 +70,7 @@ public abstract class AbstractSimpleSqlTimeseriesDao Date: Fri, 31 Jan 2020 16:33:02 +0200 Subject: [PATCH 183/261] fixed: bug related to PROD-112, PROD-132 --- ui/package-lock.json | 2374 ++++++++--------- ui/src/app/widget/lib/canvas-digital-gauge.js | 4 +- 2 files changed, 1117 insertions(+), 1261 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 74db613d09..1754af373a 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1673,12 +1673,46 @@ "glob-to-regexp": "^0.3.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + } + } + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1710,17 +1744,61 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, "@types/node": { "version": "12.0.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.10.tgz", "integrity": "sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ==", "dev": true }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-2.0.0.tgz", + "integrity": "sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==", + "dev": true, + "requires": { + "vfile-message": "*" + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -2438,17 +2516,18 @@ } }, "autoprefixer": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", - "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", + "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", "dev": true, "requires": { - "browserslist": "^2.11.3", - "caniuse-lite": "^1.0.30000805", + "browserslist": "^4.8.3", + "caniuse-lite": "^1.0.30001020", + "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^6.0.17", - "postcss-value-parser": "^3.2.3" + "postcss": "^7.0.26", + "postcss-value-parser": "^4.0.2" }, "dependencies": { "ansi-styles": { @@ -2461,15 +2540,22 @@ } }, "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.5.tgz", + "integrity": "sha512-4LMHuicxkabIB+n9874jZX/az1IaZ5a+EUuvD7KFOu9x/Bd5YHyO0DIz2ls/Kl8g0ItS4X/ilEgf4T1Br0lgSg==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" + "caniuse-lite": "^1.0.30001022", + "electron-to-chromium": "^1.3.338", + "node-releases": "^1.1.46" } }, + "caniuse-lite": { + "version": "1.0.30001023", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001023.tgz", + "integrity": "sha512-C5TDMiYG11EOhVOA62W1p3UsJ2z4DsHtMBQtjzp3ZsUglcQn62WOUgW0y795c7A5uZ+GCEIvzkMatLIlAsbNTA==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2481,21 +2567,53 @@ "supports-color": "^5.3.0" } }, + "electron-to-chromium": { + "version": "1.3.341", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.341.tgz", + "integrity": "sha512-iezlV55/tan1rvdvt7yg7VHRSkt+sKfzQ16wTDqTbQqtl4+pSUkKPXpQHDvEt0c7gKcUHHwUbffOgXz6bn096g==", + "dev": true + }, + "node-releases": { + "version": "1.1.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.47.tgz", + "integrity": "sha512-k4xjVPx5FpwBUj0Gw7uvFOTF4Ep8Hok1I6qjwL3pLfwe7Y0REQSAqOwwv9TWBCUtMHxcXfY4PgRLRozcChvTcA==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "source-map": { @@ -2668,9 +2786,9 @@ } }, "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", "dev": true }, "balanced-match": { @@ -3186,9 +3304,9 @@ "dev": true }, "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, "chain-function": { @@ -3214,27 +3332,27 @@ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, "character-entities": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", - "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", "dev": true }, "character-entities-html4": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", - "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", "dev": true }, "character-entities-legacy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", - "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", "dev": true }, "character-reference-invalid": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", - "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, "chardet": { @@ -3287,12 +3405,6 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", - "dev": true - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -3435,13 +3547,12 @@ } }, "clone-regexp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha1-BRgFzTMXM3XYIRj8CRhgbaOf1g8=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", "dev": true, "requires": { - "is-regexp": "^1.0.0", - "is-supported-regexp-flag": "^1.0.0" + "is-regexp": "^2.0.0" } }, "code-point-at": { @@ -3451,9 +3562,9 @@ "dev": true }, "collapse-white-space": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", - "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", "dev": true }, "collection-visit": { @@ -4595,7 +4706,7 @@ "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "dev": true, "requires": { "is-obj": "^1.0.0" @@ -5446,12 +5557,12 @@ } }, "execall": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", - "integrity": "sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", "dev": true, "requires": { - "clone-regexp": "^1.0.0" + "clone-regexp": "^2.1.0" } }, "expand-brackets": { @@ -5489,48 +5600,6 @@ } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -5744,6 +5813,15 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -5825,12 +5903,6 @@ "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", "dev": true }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", @@ -5974,15 +6046,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -6712,42 +6775,6 @@ "path-is-absolute": "^1.0.0" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -6935,6 +6962,12 @@ "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -7212,9 +7245,9 @@ } }, "html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "dev": true }, "html-webpack-plugin": { @@ -7871,6 +7904,12 @@ } } }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -8069,9 +8108,9 @@ } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, "is-alphanumeric": { @@ -8081,9 +8120,9 @@ "dev": true }, "is-alphanumerical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", - "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", @@ -8133,9 +8172,9 @@ "dev": true }, "is-decimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", - "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "dev": true }, "is-descriptor": { @@ -8163,21 +8202,6 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -8214,9 +8238,9 @@ } }, "is-hexadecimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", - "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, "is-number": { @@ -8281,18 +8305,6 @@ } } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -8308,9 +8320,9 @@ } }, "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", "dev": true }, "is-stream": { @@ -8318,12 +8330,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, - "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": "sha1-Ie4WUY0sHdPt0+mg1X5QIHrDZMo=", - "dev": true - }, "is-symbol": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", @@ -8346,9 +8352,9 @@ "dev": true }, "is-whitespace-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", - "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "dev": true }, "is-windows": { @@ -8358,9 +8364,9 @@ "dev": true }, "is-word-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", - "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", "dev": true }, "is-wsl": { @@ -8476,24 +8482,6 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - } - } - }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -8616,9 +8604,9 @@ } }, "known-css-properties": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.5.0.tgz", - "integrity": "sha512-LOS0CoS8zcZnB1EjLw4LLqDXw8nvt3AGH5dXLQP3D9O1nLLA+9GC5GnPl5mmF+JiQAtSX4VyZC7KvEtcA4kUtA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.17.0.tgz", + "integrity": "sha512-Vi3nxDGMm/z+lAaCjvAR1u+7fiv+sG6gU/iYDj5QOF8h76ytK9EW/EKfF0NeTyiGBi8Jy6Hklty/vxISrLox3w==", "dev": true }, "lcid": { @@ -8709,6 +8697,12 @@ } } }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -8719,6 +8713,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -8822,11 +8822,29 @@ "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, + "lodash.isregexp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isregexp/-/lodash.isregexp-4.0.1.tgz", + "integrity": "sha1-4T5kezDNVZdSoEzZEghvr32hwws=", + "dev": true + }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", + "dev": true + }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -8854,18 +8872,18 @@ "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^2.4.2" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -8885,7 +8903,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -8900,9 +8918,9 @@ "dev": true }, "longest-streak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", - "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, "loose-envify": { @@ -9000,9 +9018,9 @@ } }, "markdown-escapes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", - "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", "dev": true }, "markdown-table": { @@ -9049,16 +9067,10 @@ "prop-types": "^15.5.10" } }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, "mathml-tag-names": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz", - "integrity": "sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, "md-color-picker": { @@ -9097,9 +9109,9 @@ "from": "git://github.com/alenaksu/mdPickers.git#0.7.5" }, "mdast-util-compact": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz", - "integrity": "sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" @@ -9267,6 +9279,12 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "mini-css-extract-plugin": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", @@ -9305,9 +9323,9 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha1-+6TIGRM54T7PTWG+sD8HAQPz2VQ=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.0.2.tgz", + "integrity": "sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w==", "dev": true, "requires": { "arrify": "^1.0.1", @@ -10217,16 +10235,6 @@ "es-abstract": "^1.5.1" } }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", @@ -10574,39 +10582,10 @@ "is-hexadecimal": "^1.0.0" } }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { "error-ex": "^1.2.0" @@ -10717,6 +10696,12 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -10831,52 +10816,30 @@ } }, "postcss-html": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.12.0.tgz", - "integrity": "sha512-KxKUpj7AY7nlCbLcTOYxdfJnGE7QFAfU2n95ADj1Q90RM/pOLdz8k3n4avOyRFs7MDQHcRzJQWM1dehCwJxisQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "requires": { + "htmlparser2": "^3.10.0" + } + }, + "postcss-jsx": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.4.tgz", + "integrity": "sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==", "dev": true, "requires": { - "htmlparser2": "^3.9.2", - "remark": "^8.0.0", - "unist-util-find-all-after": "^1.0.1" + "@babel/core": ">=7.2.2" } }, "postcss-less": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-1.1.5.tgz", - "integrity": "sha512-QQIiIqgEjNnquc0d4b6HDOSFZxbFQoy4MPpli2lSLpKhMyBkKwwca2HFqu4xzxlKID/F2fxSOowwtKpgczhF7A==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", "dev": true, "requires": { - "postcss": "^5.2.16" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } + "postcss": "^7.0.14" } }, "postcss-load-config": { @@ -10992,6 +10955,16 @@ } } }, + "postcss-markdown": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", + "dev": true, + "requires": { + "remark": "^10.0.1", + "unist-util-find-all-after": "^1.0.2" + } + }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -11040,21 +11013,21 @@ } }, "postcss-reporter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-5.0.0.tgz", - "integrity": "sha512-rBkDbaHAu5uywbCR2XE8a25tats3xSOsGNx6mppK6Q9kSFGKc/FyAzfci+fWM2l+K402p1D0pNcfDGxeje5IKg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", "dev": true, "requires": { - "chalk": "^2.0.1", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "postcss": "^6.0.8" + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11071,27 +11044,19 @@ "supports-color": "^5.3.0" } }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "chalk": "^2.0.1" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11106,76 +11071,28 @@ "dev": true }, "postcss-safe-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-3.0.1.tgz", - "integrity": "sha1-t1Pv9sfArqXoN1++TN6L+QY/8UI=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz", + "integrity": "sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==", "dev": true, "requires": { - "postcss": "^6.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.0" } }, "postcss-sass": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.2.0.tgz", - "integrity": "sha512-cUmYzkP747fPCQE6d+CH2l1L4VSyIlAzZsok3HPjb5Gzsq3jE+VjpAdGlPsnQ310WKWI42sw+ar0UNN59/f3hg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.2.tgz", + "integrity": "sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ==", "dev": true, "requires": { - "gonzales-pe": "^4.0.3", - "postcss": "^6.0.6" + "gonzales-pe": "^4.2.4", + "postcss": "^7.0.21" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11190,29 +11107,40 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "supports-color": "^6.1.0" } }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11221,60 +11149,12 @@ } }, "postcss-scss": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.6.tgz", - "integrity": "sha512-4EFYGHcEw+H3E06PT/pQQri06u/1VIIPjeJQaM8skB80vZuXMhp4cSNV5azmdNkontnOID/XYWEvEEELLFB1ww==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", + "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", "dev": true, "requires": { - "postcss": "^6.0.23" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.0" } }, "postcss-selector-parser": { @@ -11306,6 +11186,12 @@ } } }, + "postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true + }, "postcss-value-parser": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz", @@ -11324,12 +11210,6 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", @@ -11530,9 +11410,9 @@ "dev": true }, "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, "raf": { @@ -11543,37 +11423,6 @@ "performance-now": "^2.1.0" } }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha1-j99oIxz/qQvC+UYDkKDLdKKbKak=", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -12264,15 +12113,6 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -12302,20 +12142,20 @@ "dev": true }, "remark": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-8.0.0.tgz", - "integrity": "sha512-K0PTsaZvJlXTl9DN6qYlvjTkqSZBFELhROZMrblm2rB+085flN84nz4g/BscKRMqDvhzlK1oQ/xnWQumdeNZYw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", "dev": true, "requires": { - "remark-parse": "^4.0.0", - "remark-stringify": "^4.0.0", - "unified": "^6.0.0" + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" } }, "remark-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-4.0.0.tgz", - "integrity": "sha512-XZgICP2gJ1MHU7+vQaRM+VA9HEL3X253uwUM/BGgx3iv6TH2B3bF3B8q00DKcyP9YrJV+/7WOWEWBFF/u8cIsw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", "dev": true, "requires": { "collapse-white-space": "^1.0.2", @@ -12324,7 +12164,7 @@ "is-whitespace-character": "^1.0.0", "is-word-character": "^1.0.0", "markdown-escapes": "^1.0.0", - "parse-entities": "^1.0.2", + "parse-entities": "^1.1.0", "repeat-string": "^1.5.4", "state-toggle": "^1.0.0", "trim": "0.0.1", @@ -12336,9 +12176,9 @@ } }, "remark-stringify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-4.0.0.tgz", - "integrity": "sha512-xLuyKTnuQer3ke9hkU38SUYLiTmS078QOnoFavztmbt/pAJtNSkNtFgR0U//uCcmG0qnyxao+PDuatQav46F1w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -12438,12 +12278,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=", - "dev": true - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -12543,6 +12377,12 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", @@ -12586,6 +12426,12 @@ "is-promise": "^2.1.0" } }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -13309,9 +13155,9 @@ } }, "specificity": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.3.2.tgz", - "integrity": "sha512-Nc/QN/A425Qog7j9aHmwOrlwX2e7pNI47ciwxwy4jOlvbbMHkNNJchit+FX+UjF3IAdiaaV5BKeWuDUnws6G1A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", "dev": true }, "split-string": { @@ -13375,9 +13221,9 @@ "dev": true }, "state-toggle": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", - "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", "dev": true }, "static-extend": { @@ -13497,7 +13343,7 @@ "stringify-entities": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha1-qYQX5Ucf0iez5F09sYYcEcr2aPc=", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", @@ -13570,402 +13416,435 @@ "dev": true }, "stylelint": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.4.0.tgz", - "integrity": "sha512-56hPH5mTFnk8LzlEuTWq0epa34fHuS54UFYQidBOFt563RJBNi1nz1F2HK2MoT1X1waq47milvRsRahFCCJs/Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.0.0.tgz", + "integrity": "sha512-6sjgOJbM3iLhnUtmRO0J1vvxie9VnhIZX/2fCehjylv9Gl9u0ytehGCTm9Lhw2p1F8yaNZn5UprvhCB8C3g/Tg==", "dev": true, "requires": { - "autoprefixer": "^7.1.2", + "autoprefixer": "^9.7.3", "balanced-match": "^1.0.0", - "chalk": "^2.0.1", - "cosmiconfig": "^3.1.0", - "debug": "^3.0.0", - "execall": "^1.0.0", - "file-entry-cache": "^2.0.0", - "get-stdin": "^5.0.1", - "globby": "^7.0.0", + "chalk": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "execall": "^2.0.0", + "file-entry-cache": "^5.0.1", + "get-stdin": "^7.0.0", + "global-modules": "^2.0.0", + "globby": "^11.0.0", "globjoin": "^0.1.4", - "html-tags": "^2.0.0", - "ignore": "^3.3.3", + "html-tags": "^3.1.0", + "ignore": "^5.1.4", + "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.5.0", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "mathml-tag-names": "^2.0.1", - "meow": "^4.0.0", - "micromatch": "^2.3.11", + "known-css-properties": "^0.17.0", + "leven": "^3.1.0", + "lodash": "^4.17.15", + "log-symbols": "^3.0.0", + "mathml-tag-names": "^2.1.1", + "meow": "^6.0.0", + "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", - "pify": "^3.0.0", - "postcss": "^6.0.6", - "postcss-html": "^0.12.0", - "postcss-less": "^1.1.0", + "postcss": "^7.0.26", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.3", + "postcss-less": "^3.1.4", + "postcss-markdown": "^0.36.0", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^5.0.0", + "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^3.0.1", - "postcss-sass": "^0.2.0", - "postcss-scss": "^1.0.2", + "postcss-safe-parser": "^4.0.1", + "postcss-sass": "^0.4.2", + "postcss-scss": "^2.0.0", "postcss-selector-parser": "^3.1.0", - "postcss-value-parser": "^3.3.0", - "resolve-from": "^4.0.0", - "specificity": "^0.3.1", - "string-width": "^2.1.0", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.0.2", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", "style-search": "^0.1.0", - "sugarss": "^1.0.0", + "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^4.0.1" + "table": "^5.4.6", + "v8-compile-cache": "^2.1.0", + "write-file-atomic": "^3.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "fill-range": "^7.0.1" } }, "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.1.1.tgz", + "integrity": "sha512-kEPCddRFChEzO0d6w61yh0WbBiSv9gBnfZWGfXRYPlGqIdIGef6HMR6pgqVSEWCYkrp8B0AtEpEXNY+Jx0xk1A==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "cosmiconfig": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-3.1.0.tgz", - "integrity": "sha512-zedsBhLSbPBms+kE7AH4vHg6JsKDz6epSv2/+5XHs8ILHlgDciSJfSWf8sX9aQ52Jb7KI7VswUTsLpR/G0cr2Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^3.0.0", - "require-from-string": "^2.0.1" + "color-name": "~1.1.4" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "ms": "^2.1.1" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "ms": "^2.1.1" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "path-type": "^4.0.0" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "fast-glob": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", + "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "to-regex-range": "^5.0.1" } }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", "dev": true }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "is-glob": "^4.0.1" } }, "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", + "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "dependencies": { - "parse-json": { + "resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true } } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "dev": true, - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "meow": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.0.0.tgz", + "integrity": "sha512-x4rYsjigPBDAxY+BGuK83YLhUIqui5wYyZoqb6QJCUOs+0fiYq+i/NV4Jt8OgIfObZFxG9iTyvLDu4UTohGTFw==", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.1.1", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.0.0", + "minimist-options": "^4.0.1", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.0", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.8.1", + "yargs-parser": "^16.1.0" } }, - "p-limit": { + "merge2": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", + "dev": true }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "parse-json": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-3.0.0.tgz", - "integrity": "sha1-+m9HsY4jgm6tMvJj50TQ4ehH+xM=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "error-ex": "^1.3.1" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "supports-color": "^6.1.0" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-selector-parser": { @@ -13980,460 +13859,232 @@ } }, "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", "dev": true }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" } }, "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "dev": true, - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" - } - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", - "dev": true - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, - "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - } - } - }, - "stylelint-config-recommended": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz", - "integrity": "sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==", - "dev": true - }, - "stylelint-config-recommended-scss": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-3.3.0.tgz", - "integrity": "sha512-BvuuLYwoet8JutOP7K1a8YaiENN+0HQn390eDi0SWe1h7Uhx6O3GUQ6Ubgie9b/AmHX4Btmp+ZzVGbzriFTBcA==", - "dev": true, - "requires": { - "stylelint-config-recommended": "^2.2.0" - } - }, - "stylelint-config-standard": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-18.3.0.tgz", - "integrity": "sha512-Tdc/TFeddjjy64LvjPau9SsfVRexmTFqUhnMBrzz07J4p2dVQtmpncRF/o8yZn8ugA3Ut43E6o1GtjX80TFytw==", - "dev": true, - "requires": { - "stylelint-config-recommended": "^2.2.0" - } - }, - "stylelint-order": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-3.0.1.tgz", - "integrity": "sha512-isVEJ1oUoVB7bb5pYop96KYOac4c+tLOqa5dPtAEwAwQUVSbi7OPFbfaCclcTjOlXicymasLpwhRirhFWh93yw==", - "dev": true, - "requires": { - "lodash": "^4.17.14", - "postcss": "^7.0.17", - "postcss-sorting": "^5.0.1" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } - } - }, - "stylelint-scss": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.9.2.tgz", - "integrity": "sha512-VUh173p3T1qJf016P7yeJ6nxkUpqF5qQ+VSDw3J8P6wEJbA1loaNgBHR3k3skHvUkF+9brLO1ibCHA00pjW3cw==", - "dev": true, - "requires": { - "lodash": "^4.17.11", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.0" - } - }, - "stylelint-webpack-plugin": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-0.10.5.tgz", - "integrity": "sha1-C24NNz/14DuqgZfr4PJiWYG9Jms=", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "ramda": "^0.25.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "ansi-regex": "^5.0.0" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", "dev": true, "requires": { - "kind-of": "^6.0.0" + "min-indent": "^1.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "has-flag": "^4.0.0" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" } } } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "trim-newlines": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", "dev": true }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "yargs-parser": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", + "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, - "sugarss": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.1.tgz", - "integrity": "sha512-3qgLZytikQQEVn1/FrhY7B68gPUUGY3R1Q1vTiD5xT+Ti1DP/8iZuwFet9ONs5+bmL8pZoDQ6JrQHVgrNlK6mA==", + "stylelint-config-recommended": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", + "integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==", + "dev": true + }, + "stylelint-config-recommended-scss": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.1.0.tgz", + "integrity": "sha512-4012ca0weVi92epm3RRBRZcRJIyl5vJjJ/tJAKng+Qat5+cnmuCwyOI2vXkKdjNfGd0gvzyKCKEkvTMDcbtd7Q==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^3.0.0" + } + }, + "stylelint-config-standard": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-19.0.0.tgz", + "integrity": "sha512-VvcODsL1PryzpYteWZo2YaA5vU/pWfjqBpOvmeA8iB2MteZ/ZhI1O4hnrWMidsS4vmEJpKtjdhLdfGJmmZm6Cg==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^3.0.0" + } + }, + "stylelint-order": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-4.0.0.tgz", + "integrity": "sha512-bXV0v+jfB0+JKsqIn3mLglg1Dj2QCYkFHNfL1c+rVMEmruZmW5LUqT/ARBERfBm8SFtCuXpEdatidw/3IkcoiA==", "dev": true, "requires": { - "postcss": "^6.0.14" + "lodash": "^4.17.15", + "postcss": "^7.0.26", + "postcss-sorting": "^5.0.1" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -14448,29 +14099,46 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", "dev": true, "requires": { - "chalk": "^2.4.1", + "chalk": "^2.4.2", "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "supports-color": "^6.1.0" } }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -14478,6 +14146,110 @@ } } }, + "stylelint-scss": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.13.0.tgz", + "integrity": "sha512-SaLnvQyndaPcsgVJsMh6zJ1uKVzkRZJx+Wg/stzoB1mTBdEmGketbHrGbMQNymzH/0mJ06zDSpeCDvNxqIJE5A==", + "dev": true, + "requires": { + "lodash.isboolean": "^3.0.3", + "lodash.isregexp": "^4.0.1", + "lodash.isstring": "^4.0.1", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "postcss-value-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz", + "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", + "dev": true + } + } + }, + "stylelint-webpack-plugin": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-1.2.1.tgz", + "integrity": "sha512-J2CFUliPYxirP8l4HUOZmKNMW6HETFPX6wxlQIlfddfV74GFaK6wDk31306LdA5bc8MOOCSsDg4u3FYVlFtF3A==", + "dev": true, + "requires": { + "arrify": "^2.0.1", + "micromatch": "^4.0.2", + "schema-utils": "^2.6.1" + }, + "dependencies": { + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "schema-utils": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -14827,15 +14599,15 @@ "dev": true }, "trim-trailing-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", - "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", "dev": true }, "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, "true-case-path": { @@ -14911,6 +14683,12 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -14927,6 +14705,15 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typeface-roboto": { "version": "0.0.22", "resolved": "https://registry.npmjs.org/typeface-roboto/-/typeface-roboto-0.0.22.tgz", @@ -15066,13 +14853,13 @@ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" }, "unherit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", - "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, "unicode-canonical-property-names-ecmascript": { @@ -15104,16 +14891,18 @@ "dev": true }, "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha1-f71jD3GRJtZ9QMZEt+P2FwNfbbo=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", "dev": true, "requires": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", "bail": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^1.1.0", "trough": "^1.0.0", - "vfile": "^2.0.0", + "vfile": "^3.0.0", "x-is-string": "^0.1.0" } }, @@ -15154,9 +14943,9 @@ } }, "unist-util-find-all-after": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.4.tgz", - "integrity": "sha512-CaxvMjTd+yF93BKLJvZnEfqdM7fgEACsIpQqz8vIj9CJnUb9VpyymFS3tg6TCtgrF7vfCJBF5jbT2Ox9CBRYRQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz", + "integrity": "sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==", "dev": true, "requires": { "unist-util-is": "^3.0.0" @@ -15169,19 +14958,22 @@ "dev": true }, "unist-util-remove-position": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", - "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" } }, "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": "sha1-Pzf881EnncvKdICrWIm7ioMu4cY=", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.2.tgz", + "integrity": "sha512-nK5n8OGhZ7ZgUwoUbL8uiVRwAbZyzBsB/Ddrlbu6jwwubFza4oe15KlyEaLNMXQW1svOQq4xesUeqA85YrIUQA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } }, "unist-util-visit": { "version": "1.4.1", @@ -15458,30 +15250,54 @@ } }, "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha1-5i2OcrIOg8MkvGxnJ47ickiL+Eo=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", "dev": true, "requires": { - "is-buffer": "^1.1.4", + "is-buffer": "^2.0.0", "replace-ext": "1.0.0", "unist-util-stringify-position": "^1.0.0", "vfile-message": "^1.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "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==", + "dev": true + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + } } }, "vfile-location": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", - "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", "dev": true }, "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.2.tgz", + "integrity": "sha512-gNV2Y2fDvDOOqq8bEe7cF3DXU6QgV4uA9zMR2P8tix11l1r7zju3zry3wZ8sx+BEfuO6WQ7z2QzfWTvqHQiwsA==", "dev": true, "requires": { - "unist-util-stringify-position": "^1.1.1" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } }, "vm-browserify": { @@ -16642,6 +16458,18 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", @@ -16674,6 +16502,32 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, + "yaml": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.3.tgz", + "integrity": "sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + } + } + }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index e9a7dfc4df..71f40d77fa 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -209,7 +209,9 @@ export default class TbCanvasDigitalGauge { } var value = tvPair[1]; if(value !== this.gauge.value) { - this.gauge._value = value; + if (!this.ctx.settings.animation) { + this.gauge._value = value; + } this.gauge.value = value; } else if (this.localSettings.showTimestamp && this.gauge.timestamp != timestamp) { this.gauge.timestamp = timestamp; From 7008f12a384adbd6ff43a6f036486edee98fd68c Mon Sep 17 00:00:00 2001 From: fumil Date: Wed, 5 Feb 2020 23:13:00 +0200 Subject: [PATCH 184/261] Added Romanian Language Contributors: lmax25 - lmax25@gmail.com fumil - emil_saracutu@yahoo.com --- ui/src/app/locale/locale.constant-ro_RO.json | 1815 ++++++++++++++++++ 1 file changed, 1815 insertions(+) create mode 100644 ui/src/app/locale/locale.constant-ro_RO.json diff --git a/ui/src/app/locale/locale.constant-ro_RO.json b/ui/src/app/locale/locale.constant-ro_RO.json new file mode 100644 index 0000000000..071e6dff3b --- /dev/null +++ b/ui/src/app/locale/locale.constant-ro_RO.json @@ -0,0 +1,1815 @@ +{ + "access": { + "unauthorized": "Neautorizat", + "unauthorized-access": "Acces Neautorizat", + "unauthorized-access-text": "Pentru a accesa această resursă, utilizatorul trebuie să fie identificat", + "access-forbidden": "Acces Interzis", + "access-forbidden-text": "Nu ai drept de acces la această resursă!
Pentru a obţine accesul, identifică-te cu alt nume de utilizator", + "refresh-token-expired": "Sesiunea a expirat", + "refresh-token-failed": "Sesiunea nu poate fi reîncărcată" + }, + "action": { + "activate": "Activează", + "suspend": "Suspendă", + "save": "Salvează", + "saveAs": "Salvează Cu Alt Nume", + "cancel": "Renunţă", + "ok": "OK", + "delete": "Şterge", + "add": "Adaugă", + "yes": "Da", + "no": "Nu", + "update": "Actualizează", + "remove": "Elimină", + "search": "Caută", + "clear-search": "Resetează Căutarea", + "assign": "Repartizează", + "unassign": "Şterge Repartizarea", + "share": "Partajare", + "make-private": "Declară Privat", + "apply": "Aplică", + "apply-changes": "Aplică Schimbările", + "edit-mode": "Mod Editare", + "enter-edit-mode": "Mod Editare", + "decline-changes": "Refuză Schimbările", + "close": "Închide", + "back": "Înapoi", + "run": "Execută-Rulează", + "sign-in": "Înregistrează Cont Nou", + "edit": "Editează", + "view": "Vizualizează", + "create": "Creează", + "drag": "Trage", + "refresh": "Reactualizează", + "undo": "Anulează Ultima Comandă", + "copy": "Copiere", + "paste": "Lipire", + "copy-reference": "Copiere Referință", + "paste-reference": "Lipire Referință", + "import": "Import", + "export": "Export", + "share-via": "Distribuie prin {{provider}}", + "continue": "Continuă", + "discard-changes": "Anulează Schimbări" + }, + "aggregation": { + "aggregation": "Agregare", + "function": "Funcţie Agregare Date", + "limit": "Valori Maxime", + "group-interval": "Interval Grupare", + "min": "Minim", + "max": "Maxim", + "avg": "Medie", + "sum": "Sumă", + "count": "Numără", + "none": "Nimic" + }, + "admin": { + "general": "General", + "general-settings": "Setări Generale", + "outgoing-mail": " Server eMail", + "outgoing-mail-settings": "Setări Pentru : Outgoing Mail Server", + "system-settings": "Setări Sistem", + "test-mail-sent": "Mesajul de test setări pentru email a fost trimis cu succes", + "base-url": "Adresa De Bază URL", + "base-url-required": "Adresa de bază URL este obligatorie", + "mail-from": "Mesaj eMail de la expeditor", + "mail-from-required": "Adresa eMail a expeditorului este obligatorie", + "smtp-protocol": "Setări Protocol SMTP", + "smtp-host": "Adresă SMTP", + "smtp-host-required": "Adresa SMTP este obligatorie", + "smtp-port": "Port SMTP", + "smtp-port-required": "Trebuie să precizaţi un port SMTP", + "smtp-port-invalid": "Textul introdus nu pare să fie al unui port SMTP", + "timeout-msec": "Timp expirare (milisecunde)", + "timeout-required": "Timpul de expirare este obligatoriu", + "timeout-invalid": "Timpul de expirare nu pare să fie valid", + "enable-tls": "Permite TLS", + "send-test-mail": "Trimite mesaj eMail test", + "security-settings": "Setări Securitate", + "password-policy": "Reguli Pentru Definirea Parolei", + "minimum-password-length": "Numărul Minim De Caractere Al Parolei", + "minimum-password-length-required": "Numărul minim de caractere al parolei este obligatoriu", + "minimum-password-length-range": "Numărul minim de caractere al parolei trebuie să fie între 5 - 50", + "minimum-uppercase-letters": "Numărul Minim De Caractere Scrise Cu MAJUSCULĂ Din Parolă", + "minimum-uppercase-letters-range": "Numărul minim de caractere scrise cu majusculă din parolă nu poate fi negativ", + "minimum-lowercase-letters": "Numărul Minim De Caractere Scrise Cu Literă mică Din Parolă", + "minimum-lowercase-letters-range": "Numărul minim de caractere scrise cu literă mică din parolă nu poate fi negativ", + "minimum-digits": "Numărul Minim De Cifre Din Parolă", + "minimum-digits-range": "Numărul minim de cifre din parolă nu poate fi negativ", + "minimum-special-characters": "Numărul Minim De Caractere Speciale Din Parolă", + "minimum-special-characters-range": "Numărul minim de caractere speciale din parolă nu poate fi negativ", + "password-expiration-period-days": "Perioada de expirare a parolei (zile)", + "password-expiration-period-days-range": "Perioada de expirare a parolei (zile) nu poate fi negativă", + "password-reuse-frequency-days": "Frecvenţa de refolosire a parolei (zile)", + "password-reuse-frequency-days-range": "Frecvenţa de refolosire a parolei(zile) nu poate fi negativă", + "general-policy": "Reguli Generale", + "max-failed-login-attempts": "Numărul maxim de încercări eşuate de accesare a paginii înainte de blocarea contului", + "minimum-max-failed-login-attempts-range": "Numărul maxim de încercări eşuate de accesare a paginii nu poate fi negativ", + "user-lockout-notification-email": "În Cazul Blocării Contului, Trimite eMail De Notificare" + }, + "alarm": { + "alarm": "Alarmă", + "alarms": "Alarme", + "select-alarm": "Selectează Alarmă", + "no-alarms-matching": "Nu au fost găsite alarme pentru '{{entity}}'", + "alarm-required": "Alarma este obligatorie", + "alarm-status": "Stare Alarmă", + "search-status": { + "ANY": "Oricare", + "ACTIVE": "Activă", + "CLEARED": "Ştearsă", + "ACK": "Observată", + "UNACK": "Neobservată" + }, + "display-status": { + "ACTIVE_UNACK": "Activă Neobservată", + "ACTIVE_ACK": "Activă Observată", + "CLEARED_UNACK": "Ștearsă Neobservată", + "CLEARED_ACK": "Ștearsă Observată" + }, + "no-alarms-prompt": "NiciO Alarmă Găsită", + "created-time": "Data Creării", + "type": "Tipul", + "severity": "Urgenţa", + "originator": "Iniţiator", + "originator-type": "Tip Iniţiator", + "details": "Detalii", + "status": "Stare", + "alarm-details": "Detalii Alarmă", + "start-time": "Început", + "end-time": "Sfârşit", + "ack-time": "Data Observării", + "clear-time": "Data Ştergerii", + "severity-critical": "Critică", + "severity-major": "Majoră", + "severity-minor": "Minoră", + "severity-warning": "Avertizare", + "severity-indeterminate": "Nedeterminată", + "acknowledge": "Marchează Observat", + "clear": "Şterge", + "search": "Caută Alarme", + "selected-alarms": "{ count, plural, 1 {o alarmă} other {# alarme} } selectate", + "no-data": "Nu există date de afişat", + "polling-interval": "Interval actualizare alarme (secunde)", + "polling-interval-required": "Intervalul pentru actualizarea alarmelor este obligatoriu", + "min-polling-interval-message": "Valoarea minimă permisă pentru interval actualizare alarme este o secundă", + "aknowledge-alarms-title": "Ai selectat { count, plural, 1 {o alarmă} other {# alarme} }", + "aknowledge-alarms-text": "Sigur vrei să marchezi ca 'Observat' { count, plural, 1 {o alarmă} other {# alarme} }?", + "aknowledge-alarm-title": "Alarmă Observată", + "aknowledge-alarm-text": "Sigur vrei să marchezi alarma ca 'Observat'?", + "clear-alarms-title": "Şterge { count, plural, 1 {o alarmă} other {# alarme} }", + "clear-alarms-text": "Sigur vrei să ștergi { count, plural, 1 {o alarmă} other {# alarme} }?", + "clear-alarm-title": "Şterge Alarma", + "clear-alarm-text": "Sigur vrei să ștergi alarma?", + "alarm-status-filter": "Stare Filtre Alarmă", + "max-count-load": "Număr maxim de alarme înregistrate (0=nelimitat)", + "max-count-load-required": "Numărul maxim de alarme înregistrate este obligatoriu", + "max-count-load-error-min": "Numărul maxim de alarme este 0", + "fetch-size": "Număr alarme afişate", + "fetch-size-required": "Numărul alarmelor afişate este obligatoriu", + "fetch-size-error-min": "Valoarea minimă este 10" + }, + "alias": { + "add": "Adaugă Pseudonim", + "edit": "Editează Pseudonim", + "name": "Denumire Pseudonim", + "name-required": "Denumirea pseudonimului este obligatorie", + "duplicate-alias": "Există deja un pseudonim cu aceeaşi denumire", + "filter-type-single-entity": "O Singură Entitate", + "filter-type-entity-list": "Listă Entităţi", + "filter-type-entity-name": "Nume Entitate", + "filter-type-state-entity": "Entitate din starea panoului", + "filter-type-state-entity-description": "Entitate luată din parametrii stării panoului", + "filter-type-asset-type": "Tip Proprietate", + "filter-type-asset-type-description": "Proprietăţi de tip: '{{assetType}}'", + "filter-type-asset-type-and-name-description": "proprietăţi de tipul: '{{assetType}}' a căror denumire începe cu: '{{prefix}}'", + "filter-type-device-type": "Tip Dispozitiv", + "filter-type-device-type-description": "Dispozitive tip: '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Dispozitive Tip: '{{deviceType}}' a căror denumire începe cu: '{{prefix}}'", + "filter-type-entity-view-type": "Tip entitate definită", + "filter-type-entity-view-type-description": "Tip entități definite: '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Entități definite de tip: '{{entityView}}' a căror denumire începe cu : '{{prefix}}'", + "filter-type-relations-query": "Relaţii Interogare", + "filter-type-relations-query-description": "{{entities}} care au relații tip {{relationType}} în direcția {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Proprietate Asset search query", + "filter-type-asset-search-query-description": "Proprietăţi de tip {{assetTypes}} care au relații tip {{relationType}} în direcția {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Criteriu Căutare Dispozitiv", + "filter-type-device-search-query-description": "Dispozitive de tip {{deviceTypes}} care au relații tip {{relationType}} în direcția {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Criteriu căutare entitate definită", + "filter-type-entity-view-search-query-description": "Entități definite de tip {{entityViewTypes}} care au relații tip {{relationType}} în direcția {{direction}} {{rootEntity}}", + "entity-filter": "Filtru Entitate", + "resolve-multiple": "Rezolvă Ca Entităţi Multiple", + "filter-type": "Tip Filtru", + "filter-type-required": "Tipul filtrului este obligatoriu", + "entity-filter-no-entity-matched": "Nu au fost găsite entităţi corespunzătoare filtrului specificat", + "no-entity-filter-specified": "Nu a fost specificat filtru pentru entitate", + "root-state-entity": "Foloseşte Entitatea Stare Panou Ca Origine", + "root-entity": "Entitate Rădăcină", + "state-entity-parameter-name": "Nume Parametru Stare Entitate", + "default-state-entity": "Stare Entitate Implicită", + "default-entity-parameter-name": "Implicită", + "max-relation-level": "Nivel Maxim Relaţie", + "unlimited-level": "Nivel Nelimitat", + "state-entity": "Entitate Stare Panou", + "all-entities": "Toate Entităţile", + "any-relation": "Oricare" + }, + "asset": { + "asset": "Proprietate", + "assets": "Proprietăţi", + "management": "Administrare Proprietăți", + "view-assets": "Vezi Proprietăţi", + "add": "Adaugă Proprietate", + "assign-to-customer": "Repartizează Proprietate", + "assign-asset-to-customer": "Repartizează proprietăţi clientului", + "assign-asset-to-customer-text": "Selectează proprietăţile care vor fi repartizate clientului", + "no-assets-text": "Nu au fost găsite proprietăţi", + "assign-to-customer-text": "Selectează clientul căruia îi vor fi repartizate proprietăţile", + "public": "Publică", + "assignedToCustomer": "Repartizată clientului", + "make-public": "Declară proprietate publică", + "make-private": "Declară proprietate privată", + "unassign-from-customer": "Şterge repartizare client", + "delete": "Şterge proprietate", + "asset-public": "Proprietate publică", + "asset-type": "Tip Proprietate", + "asset-type-required": "Tipul proprietății este obligatoriu", + "select-asset-type": "Alege tipul proprietății", + "enter-asset-type": "Introdu tipul proprietății", + "any-asset": "Orice Proprietate", + "no-asset-types-matching": "Nu a fost găsită nicio proprietate conținând '{{entitySubtype}}'", + "asset-type-list-empty": "Nu a fost selectat niciun tip de proprietate", + "asset-types": "Tipuri Proprietate", + "name": "Nume Proprietate", + "name-required": "Numele este obligatoriu", + "description": "Descriere Proprietate", + "type": "Tip Proprietate", + "type-required": "Tipul proprietății este obligatoriu", + "details": "Detalii Proprietate", + "events": "Evenimente", + "add-asset-text": "Adaugă proprietate", + "asset-details": "Detalii proprietate", + "assign-assets": "Repartizează proprietăţi", + "assign-assets-text": "Repartizează { count, plural, 1 {o proprietate} other {# proprietăţi} } clientului", + "delete-assets": "Şterge proprietăţi", + "unassign-assets": "Şterge repartizare proprietăţi", + "unassign-assets-action-title": "Şterge repartizare { count, plural, 1 {o proprietate} other {# proprietăţi} } clientului", + "assign-new-asset": "Repartizează proprietate nouă", + "delete-asset-title": "Sigur vrei să ștergi '{{assetName}}'?", + "delete-asset-text": "ATENŢIE! După confirmare, proprietatea şi toate datele referitoare la aceasta, vor fi șterse IREVERSIBIL!", + "delete-assets-title": "Sigur vrei să ștergi { count, plural, 1 {o proprietate} other {# proprietăţi} }?", + "delete-assets-action-title": "Ştergi { count, plural, 1 {o proprietate} other {# proprietăţi} }", + "delete-assets-text": "ATENŢIE! După confirmare, toate proprietăţile selectate şi toate datele referitoare la aceastea, vor fi șterse IREVERSIBIL!", + "make-public-asset-title": "Sigur vrei ca proprietatea '{{assetName}}' să devină publică ", + "make-public-asset-text": "ATENŢIE! După confirmare, proprietatea selectată şi toate datele referitoare la aceasta vor putea fi accesate de către oricine", + "make-private-asset-title": "Sigur vrei ca proprietatea '{{assetName}}' să devină privată?", + "make-private-asset-text": "ATENŢIE! După confirmare, proprietatea selectată şi toate datele referitoare la aceasta vor putea fi accesate doar de către proprietar", + "unassign-asset-title": "Sigur vrei să ştergi repartizarea pentru proprietatea '{{assetName}}'?", + "unassign-asset-text": "ATENŢIE! După confirmare, repartizarea proprietăţii nu va mai putea fi accesată de către client", + "unassign-asset": "Şterge repartizare proprietate", + "unassign-assets-title": "Sigur vrei să ştergi repartizarea pentru { count, plural, 1 {o proprietate} other {# proprietăţi} }?", + "unassign-assets-text": "ATENŢIE! După confirmare, repartizările proprietăţilor selectate nu vor mai putea fi accesate de către client", + "copyId": "Copiază ID proprietate", + "idCopiedMessage": "ID-ul proprietăţii a fost copiat în clipboard", + "select-asset": "Selectează proprietate", + "no-assets-matching": "Nu au fost găsite proprietăţi al căror nume conține '{{entity}}'", + "asset-required": "Proprietatea este obligatorie", + "name-starts-with": "Numele proprietăţii începe cu", + "import": "Importă proprietăţi", + "asset-file": "Fişier Proprietăţi", + "label": "Eticheta" + }, + "attribute": { + "attributes": "Atribute", + "latest-telemetry": "Ultimele Date Telemetrice", + "attributes-scope": "Scop Atribute Entitate", + "scope-latest-telemetry": "Ultimele Date Telemetrice", + "scope-client": "Atribute Client", + "scope-server": "Atribute Server", + "scope-shared": "Atribute Partajate", + "add": "Adaugă Atribut", + "key": "Cheie", + "last-update-time": "Ultima Actualizare", + "key-required": "Cheia atributului este obligatorie", + "value": "Valoare", + "value-required": "Valoarea atributului este obligatorie", + "delete-attributes-title": "Sigur vrei să ștergi { count, plural, 1 {un atribut} other {# atribute} }?", + "delete-attributes-text": "ATENŢIE! După confirmare, toate atributele selectate vor fi şterse", + "delete-attributes": "Şterge Atribute", + "enter-attribute-value": "Specifică Valoarea Atributului", + "show-on-widget": "Afişează În Widget", + "widget-mode": "Modul Widget", + "next-widget": "Widget Următor ", + "prev-widget": "Widget Precedent", + "add-to-dashboard": "Adaugă în panou", + "add-widget-to-dashboard": "Adaugă Widget În Panou", + "selected-attributes": "{ count, plural, 1 {un atribut} other {# atribute} } selectate", + "selected-telemetry": "{ count, plural, 1 {o unitate telemetrică} other {# unităţi telemetrice} } selectate" + }, + "audit-log": { + "audit": "Audit", + "audit-logs": "Jurnale Audit", + "timestamp": "Cronologie", + "entity-type": "Tip Entitate", + "entity-name": "Denumire Entitate", + "user": "Utilizator", + "type": "Tip", + "status": "Stare", + "details": "Detalii", + "type-added": "Adăugat", + "type-deleted": "Şters", + "type-updated": "Actualizat", + "type-attributes-updated": "Atribute actualizate", + "type-attributes-deleted": "Atribute şterse", + "type-rpc-call": "RPC call", + "type-credentials-updated": "Acreditări actualizate", + "type-assigned-to-customer": "Repartizat către client", + "type-unassigned-from-customer": "Anulează Repartizarea Către Client", + "type-activated": "Activat", + "type-suspended": "Suspendat", + "type-credentials-read": "Acreditări citite", + "type-attributes-read": "Atribute citite", + "type-relation-add-or-update": "Relaţie actualizată", + "type-relation-delete": "Relaţie ștearsă", + "type-relations-delete": "Toate relaţiile șterse", + "type-alarm-ack": "Confirmat", + "type-alarm-clear": "Şters", + "type-login": "Intră În Cont", + "type-logout": "Parăseşte Contul", + "type-lockout": "Blocat", + "status-success": "Succes", + "status-failure": "Eșec", + "audit-log-details": "Detalii Jurnale Audit", + "no-audit-logs-prompt": "Nu Au Fost Găsite Jurnale", + "action-data": "Detalii Acțiune", + "failure-details": "Detalii Eșec", + "search": "Caută Jurnale Audit", + "clear-search": "Resetează Căutarea" + }, + "confirm-on-exit": { + "message": "Au rămas modificări nesalvate. Doriţi să părăsiţi această pagină fară a salva modificările?", + "html-message": "Au rămas modificări nesalvate.
Doriţi să părăsiţi această pagină fară a salva modificările?", + "title": "Modificări Nesalvate" + }, + "contact": { + "country": "Ţară", + "city": "Oraş", + "state": "Judeţ", + "postal-code": "Cod Poştal", + "postal-code-invalid": "Cod poștal incorect", + "address": "Adresă 1", + "address2": "Adresă 2", + "phone": "Telefon", + "email": "eMail", + "no-address": "Fără Adresă" + }, + "common": { + "username": "Nume Utilizator", + "password": "Parola", + "enter-username": "Introdu nume utilizator", + "enter-password": "Introdu parola", + "enter-search": "Definește căutarea" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "Client", + "customers": "Clienţi", + "management": "Administrare Clienţi", + "dashboard": "Panou Control Client", + "dashboards": "Panouri Control Clienţi", + "devices": "Dispozitive Client", + "entity-views": "Entități Definite Client", + "assets": "Proprietăţi Client", + "public-dashboards": "Panouri Control Publice", + "public-devices": "Dispozitive Publice", + "public-assets": "Proprietăţi Publice", + "public-entity-views": "Definiții Entități Publice Client", + "add": "Adaugă Client", + "delete": "Şterge Client", + "manage-customer-users": "Administrare Utilizatori Client", + "manage-customer-devices": "Administrare Dispozitive Client", + "manage-customer-dashboards": "Administrare Panouri Control Client", + "manage-public-devices": "Administrare Dispozitive Publice", + "manage-public-dashboards": "Administrare Panouri Control Publice", + "manage-customer-assets": "Administrare Proprietăţi Client", + "manage-public-assets": "Administrare Proprietăţi Publice", + "add-customer-text": "Adăugare Client Nou", + "no-customers-text": "Nu au fost găsiţi clienţi", + "customer-details": "Detalii Client", + "delete-customer-title": "Vrei să ștergi clientul '{{customerTitle}}'?", + "delete-customer-text": "ATENŢIE! După confirmare, clientul şi toate datele referitoare la acesta vor fi șterse IREVERSIBIL!", + "delete-customers-title": "Vrei să ștergi { count, plural, 1 {un client} other {# clienţi} }?", + "delete-customers-action-title": "Şterge { count, plural, 1 {un client} other {# clienţi} }", + "delete-customers-text": "ATENŢIE! După confirmare, toaţi clienţii selectate şi toate datele referitoare la aceaştia, vor fi șterse IREVERSIBIL!", + "manage-users": "Administrare Utilizatori", + "manage-assets": "Administrare Proprietăţi", + "manage-devices": "Administrare Dispozitive", + "manage-dashboards": "Administrare Panouri Control Publice", + "title": "Titlu", + "title-required": "Titlul este obligatoriu", + "description": "Descriere", + "details": "Detalii", + "events": "Evenimente", + "copyId": "Copie ID Client", + "idCopiedMessage": "ID Client A Fost Copiat In Clipboard", + "select-customer": "Selectează Client", + "no-customers-matching": "Niciun client nu se potrivește cu:'{{entity}}'", + "customer-required": "Clientul este obligatoriu", + "select-default-customer": "Selecteaza Client Implicit", + "default-customer": "Client Implicit", + "default-customer-required": "Clientul implicit este obligatoriu pentru a putea depana panoul de control la nivel de PROPRIETAR" + }, + "datetime": { + "date-from": "Dată început:", + "time-from": "Oră început:", + "date-to": "Dată sfârșit:", + "time-to": "Oră sfârșit:" + }, + "dashboard": { + "dashboard": "Panou", + "dashboards": "Panouri", + "management": "Administrare Panouri", + "view-dashboards": "Afişează Panouri", + "add": "Adaugă Panou", + "assign-dashboard-to-customer": "Repartizează Panou/Panouri Clientului", + "assign-dashboard-to-customer-text": "Selectează panourile care vor fi repartizate clientului", + "assign-to-customer-text": "Alege Clientul Căruia îi vor fi repartizate Panourile", + "assign-to-customer": "Repartizează Clientului", + "unassign-from-customer": "Şterge Repartizarea Către Client", + "make-public": "Declară Panou Public", + "make-private": "Declară Panou Privat", + "manage-assigned-customers": "Administrare Clienţi Repartizaţi", + "assigned-customers": "Clienţi Repartizaţi", + "assign-to-customers": "Repartizează Panouri Către Clienţi", + "assign-to-customers-text": "Alege clienţii cărora le vor fi repartizate panourile", + "unassign-from-customers": "Şterge repartizarea panourilor către clienţi", + "unassign-from-customers-text": "Alege clienţii cărora le va fi ștearsă repartizarea panourilor", + "no-dashboards-text": "Nu au fost găsite panouri", + "no-widgets": "Nu sunt widgeturi configurate", + "add-widget": "Adăugare Widget Nou", + "title": "Titlu Widget", + "select-widget-title": "Alege Widget", + "select-widget-subtitle": "Listă Tipuri Widget", + "delete": "Şterge Panou", + "title-required": "Titlul este obligatoriu", + "description": "Descriere", + "details": "Detalii", + "dashboard-details": "Detalii Panou", + "add-dashboard-text": "Adăugare Panou Nou", + "assign-dashboards": "Repartizare Panouri", + "assign-new-dashboard": "Repartizare Panou Nou", + "assign-dashboards-text": "Repartizează { count, plural, 1 {un panou} other {# panouri} } clienţilor", + "unassign-dashboards-action-text": "Şterge repartizare { count, plural, 1 {un panou} other {# panouri} } către clienți", + "delete-dashboards": "Şterge Panouri", + "unassign-dashboards": "Şterge Repartizare Panouri", + "unassign-dashboards-action-title": "Şterge Repartizare { count, plural, 1 {un panou} other {# panouri} } către client", + "delete-dashboard-title": "Vrei să ștergi panoul '{{dashboardTitle}}'?", + "delete-dashboard-text": "ATENŢIE! După confirmare, panoul și datele aferente acestuia vor fi șterse IREVERSIBIL!", + "delete-dashboards-title": "Vrei să ștergi { count, plural, 1 {un panou} other {# panouri} }?", + "delete-dashboards-action-title": "Ştergere { count, plural, 1 {un panou} other {# panouri} }", + "delete-dashboards-text": "ATENŢIE! După confirmare, panourile selectate şi datele aferente acestora vor fi șterse IREVERSIBIL!", + "unassign-dashboard-title": "Vrei să ştergi repartizarea panoului '{{dashboardTitle}}'?", + "unassign-dashboard-text": "ATENŢIE! După confirmare, panoul nu va mai putea fi accesat de către client", + "unassign-dashboard": "Şterge Repartizare Panou", + "unassign-dashboards-title": "Vrei să ştergi repartizarea a { count, plural, 1 {un panou} other {# panouri} }?", + "unassign-dashboards-text": "ATENŢIE! După confirmare, panoul nu va mai putea fi accesat de către client", + "public-dashboard-title": "Panoul a devenit public", + "public-dashboard-text": "Panoul tău {{dashboardTitle}} a devenit public şi este accesibil la link:", + "public-dashboard-notice": "Notă: Nu uitaţi să definiţi ca publice şi dispozitivele aferente acestui panou, pentru a le face vizibile", + "make-private-dashboard-title": "Doriţi să definiţi panoul '{{dashboardTitle}}' ca privat?", + "make-private-dashboard-text": "ATENŢIE! După confirmare, panoul va putea fi accesat doar de către proprietar", + "make-private-dashboard": "Declară Panou Privat", + "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", + "select-dashboard": "Selectează Panou", + "no-dashboards-matching": "Nu au fost găsite panouri al căror nume conține '{{entity}}'", + "dashboard-required": "Panoul este obligatoriu", + "select-existing": "Selectează Un Panou Existent", + "create-new": "Creează Panou Nou", + "new-dashboard-title": "Denumire Panou Nou", + "open-dashboard": "Deschide Panou", + "set-background": "Setează Culoarea De Fundal (Background)", + "background-color": "Culoarea (Background)", + "background-image": "Imaginea (Background)", + "background-size-mode": "Mod Mărime (Background)", + "no-image": "Nicio imagine selectată", + "drop-image": "Trage o imagine sau alege un fişier", + "settings": "Setări", + "columns-count": "Număr Coloane", + "columns-count-required": "Numărul coloanelor este obligatoriu", + "min-columns-count-message": "Numărul minim permis de coloane este 10", + "max-columns-count-message": "Numărul maxim permis de coloane este 1000", + "widgets-margins": "Spaţiu vertical între widgeturi", + "horizontal-margin": "Spaţiu orizontal între widget-uri", + "horizontal-margin-required": "Valoarea spaţiului orizontal este obligatorie", + "min-horizontal-margin-message": "Valoarea minimă permisă pentru spaţiul orizontal între widgeturi este 0", + "max-horizontal-margin-message": "Valoarea maximă permisă pentru spaţiul orizontal între widgeturi este 0", + "vertical-margin": "Spaţiu vertical între widgeturi", + "vertical-margin-required": "Valoarea spaţiului vertical este obligatorie", + "min-vertical-margin-message": "Valoarea minimă permisă pentru spaţiul vertical între widgeturi este 0", + "max-vertical-margin-message": "Valoarea maximă permisă pentru spaţiul vertical între widgeturi este 50", + "autofill-height": "Auto Umplere Pe Înălţime", + "mobile-layout": "Setări Pagină Pentru Dispozitive Mobile", + "mobile-row-height": "Înălţimea liniei în pagina pentru dispozitive mobile (pixeli)", + "mobile-row-height-required": "Valoarea pentru înălţimea liniei este obligatorie", + "min-mobile-row-height-message": "Valoarea minimă permisă pentru înălţimea liniei în pagina pentru dispozitive mobile este 5 pixeli", + "max-mobile-row-height-message": "Valoarea maximă permisă pentru înălţimea liniei în pagina pentru dispozitive mobile este 200 pixeli", + "display-title": "Afişează Titlul Panoului", + "toolbar-always-open": "Menţine Deschisă Bara De Instrumente", + "title-color": "Culoare Titlu", + "display-dashboards-selection": "Afişează Selecţie Panouri", + "display-entities-selection": "Afişează Selecţie Entităţi", + "display-dashboard-timewindow": "Afişează Interval", + "display-dashboard-export": "Afişează Exportul", + "import": "Importă Panou", + "export": "Exportă Panou", + "export-failed-error": "Panoul nu poate fi exportat: {{error}}", + "create-new-dashboard": "Creează Panou Nou", + "dashboard-file": "Fişier Pentru Panou", + "invalid-dashboard-file-error": "Panoul nu poate fi importat; structură de date invalidă", + "dashboard-import-missing-aliases-title": "Configurează pseudonim pentru panoul importat", + "create-new-widget": "Creează Widget Nou", + "import-widget": "Importă Widget", + "widget-file": "Fişier Pentru Widget", + "invalid-widget-file-error": "Widgetul nu poate fi importat; structură de date invalidă", + "widget-import-missing-aliases-title": "Configurează pseudonim pentru widgetul importat", + "open-toolbar": "Deschide Bara De Instrumente Pentru Panou", + "close-toolbar": "Închide Bara De Instrumente", + "configuration-error": "Eroare De Configurare", + "alias-resolution-error-title": "Eroare configurare pseudonim panou", + "invalid-aliases-config": "Nu există nici un dispozitiv corespunzător filtrului de pseudonime.
Pentru a rezolva această problemă, contactează administratorul", + "select-devices": "Selectează Dispozitiv", + "assignedToCustomer": "Repartizat Clientului", + "assignedToCustomers": "Repartizate Clienților", + "public": "Publice", + "public-link": "Adresă Pagină Publică", + "copy-public-link": "Copiază Adresa Paginii Publice", + "public-link-copied-message": "Adresa paginii publice a panoului a fost copiată în clipboard", + "manage-states": "Administrare Stări Panou", + "states": "Stări Panou", + "search-states": "Caută Stări Panou", + "selected-states": "{ count, plural, 1 {o stare panou} other {# stări panou}} selectate", + "edit-state": "Editează Stare Panou", + "delete-state": "Şterge Stare Panou", + "add-state": "Adaugă Stare Panou", + "state": "Stare Panou", + "state-name": "Denumire", + "state-name-required": "Denumirea stării panoului este obligatorie", + "state-id": "ID Stare Panou", + "state-id-required": "IDul Stării panoului este obligatoriu", + "state-id-exists": "O stare panou având acest ID este deja înregistrată", + "is-root-state": "Stare Rădăcină", + "delete-state-title": "Şterge Stare Panou", + "delete-state-text": "Sigur vrei să ștergi starea panou '{{stateName}}'?", + "show-details": "Afişează Detalii", + "hide-details": "Ascunde Detalii", + "select-state": "Selectează Stare Destinaţie", + "state-controller": "Controler Stări" + }, + "datakey": { + "settings": "Setări", + "advanced": "Avansat", + "label": "Etichetă", + "color": "Culoare", + "units": "Simbol special atașat valorii afişate", + "decimals": "Număr zecimale", + "data-generation-func": "Funcţie Generare Date", + "use-data-post-processing-func": "Utilizare Funcţie Post Procesare", + "configuration": "Configurare Chei Date", + "timeseries": "Serii Temporale", + "attributes": "Atribute", + "entity-field": "Câmp Entitate", + "alarm": "Câmpuri Alarmă", + "timeseries-required": "Seriile temporale pentru entitate sunt obligatorii", + "timeseries-or-attributes-required": "Seriile temporale sau atributele pentru entitate sunt obligatorii", + "maximum-timeseries-or-attributes": "{ count, plural, 1 { Este permisă maximum o serie temporală sau atribut} other {Sunt permise maximum # serii temporale sau atribute}}", + "alarm-fields-required": "Câmpurile pentru alarmă sunt obligatorii", + "function-types": "Tipuri Funcţie", + "function-types-required": "Tipurile de funcţie sunt obligatorii", + "maximum-function-types": "Maximum { count, plural, 1 {Este permis doar un tip de funcţie} other {Sunt permise doar # tipuri de funcţie} }", + "time-description": "Cronologie valoare curentă;", + "value-description": "valoare curentă;", + "prev-value-description": "Rezultat execuție funcţie precedentă;", + "time-prev-description": "Cronologie valoare precedentă;", + "prev-orig-value-description": "Valoare precedentă originală;" + }, + "datasource": { + "type": "Tip Sursă Date", + "name": "Denumire", + "add-datasource-prompt": "Adaugă Sursă Date" + }, + "details": { + "edit-mode": "Mod Editare", + "toggle-edit-mode": "Schimbă Mod Editare" + }, + "device": { + "device": "Dispozitiv", + "device-required": "Dispozitivul este obligatoriu", + "devices": "Dispozitive", + "management": "Administrare Dispozitive", + "view-devices": "Vizualizare Dispozitive", + "device-alias": "Pseudonim Dispozitiv", + "aliases": "Pseudonime Dispozitiv", + "no-alias-matching": "'{{alias}}' nu a fost găsit", + "no-aliases-found": "Nu au fost găsite pseudonime", + "no-key-matching": "'{{key}}' nu a fost găsită", + "no-keys-found": "Nu a fost găsită nicio cheie", + "create-new-alias": "Creează Pseudonim Nou", + "create-new-key": "Creează Cheie Nouă", + "duplicate-alias-error": "Pseudonimul ales este deja înregistrat : '{{alias}}'.
Pseudonimele dispozitivelor trebuie să fie unice în acelaşi panou", + "configure-alias": "Configurează Pseudonim: '{{alias}}'", + "no-devices-matching": "Nu au fost găsite dispozitive al căror nume conține: '{{entity}}'", + "alias": "Pseudonim", + "alias-required": "Pseudonimul pentru dispozitiv este obligatoriu", + "remove-alias": "Şterge Pseudonim Dispozitiv", + "add-alias": "Adaugă Pseudonim Dispozitiv", + "name-starts-with": "Denumirea dispozitivului începe cu: ", + "device-list": "Listă Dispozitive", + "use-device-name-filter": "Utilizează Filtru Căutare", + "device-list-empty": "Nu este selectat niciun dispozitiv", + "device-name-filter-required": "Filtrul de căutare pentru nume dispozitiv este obligatoriu", + "device-name-filter-no-device-matched": "Nu au fost găsite dispozitive al căror nume începe cu '{{device}}'", + "add": "Adaugă Dispozitiv", + "assign-to-customer": "Repartizează Clientului", + "assign-device-to-customer": "Repartizează Dispozitiv(e) Clientului", + "assign-device-to-customer-text": "Selectează dispozitivele de repartizat clientului ", + "make-public": "Configurează Ca Dispozitiv Public", + "make-private": "Configurează Ca Dispozitiv Privat", + "no-devices-text": "Nu există dispozitive", + "assign-to-customer-text": "Selectrază clientul căruia să-i fie repartizat(e) dispozitiv(ele)", + "device-details": "Detalii Dispozitiv", + "add-device-text": "Adaugă Dispozitiv Nou", + "credentials": "Acreditări", + "manage-credentials": "Administrare Acreditări", + "delete": "Şterge Dispozitiv", + "assign-devices": "Repartizeză Dispozitive", + "assign-devices-text": "Repartizeză { count, plural, 1 {un dispozitiv} other {# dispozitive} } Clientului", + "delete-devices": "Şterge Dispozitive", + "unassign-from-customer": "Şterge repartizarea către client", + "unassign-devices": "Şterge repartizarea dispozitivelor", + "unassign-devices-action-title": "Şterge repartizarea a { count, plural, 1 {un dispozitiv} other {# dispozitive} } de la client", + "assign-new-device": "Repartizare Dispozitiv Nou", + "make-public-device-title": "Sigur vrei să faci dispozitivul '{{deviceName}}' public?", + "make-public-device-text": "După confirmare, dispozitivul și toate datele aferente acestuia vor fi făcute publice, fiind deci accesibile oricui", + "make-private-device-title": "Sigur vrei să faci dispozitivul '{{deviceName}}' privat?", + "make-private-device-text": "După confirmare, dispozitivul și toate datele aferente acestuia vor fi private, deci accesibile doar proprietarului", + "view-credentials": "Vezi Credențiale", + "delete-device-title": "Sigur vrei să ștergi dispozitivul '{{deviceName}}'?", + "delete-device-text": "ATENȚIE! După confirmare, dispozitivul împreună cu datele aferente acestuia vor fi șterse IREVERSIBIL!", + "delete-devices-title": "Sigur vrei să ștergi { count, plural, 1 {un dispozitiv} other {# dispozitive} }?", + "delete-devices-action-title": "Delete { count, plural, 1 {un dispozitiv} other {# dispozitive} }", + "delete-devices-text": "ATENȚIE! După confirmare, toate dispozitivele selectate, împreună cu datele aferente acestora, vor fi șterse IREVERSIBIL!", + "unassign-device-title": "Sigur vrei să ștergi repartizarea dispozitivului '{{deviceName}}'?", + "unassign-device-text": "ATENȚIE! După confirrmare, dispozitivul nu va mai fi accesibil clientului", + "unassign-device": "Șterge repartizare dispozitiv", + "unassign-devices-title": "Sigur vrei să ștergi repartizarea pentru { count, plural, 1 {un dispozitiv} other {# devices} }?", + "unassign-devices-text": "ATENȚIE! După confirmare, repartizarea dispozitivelor va fi ștearsă, acestea nemaifiind accesibile clientului", + "device-credentials": "Credențiale Dispozitiv", + "credentials-type": "Tip Credențiale", + "access-token": "Token Acces", + "access-token-required": "Tokenul de acces este necesar", + "access-token-invalid": "Dimensiunea tokenului de acces trebuie să fie de 1-20 caractere", + "rsa-key": "Cheie RSA publică", + "rsa-key-required": "Cheia RSA publică key este necesară", + "secret": "Cod Secret", + "secret-required": "Codul secret este necesar", + "device-type": "Tip Dispozitiv", + "device-type-required": "Tipul dispozitivului este obligatoriu", + "select-device-type": "Alege tipul dispozitivului", + "enter-device-type": "Introdu tipul dispozitivului", + "any-device": "Orice Dispozitiv", + "no-device-types-matching": "Nu au fost găsite dispozitive de tip '{{entitySubtype}}' ", + "device-type-list-empty": "Nu au fost selectate tipuri dispozitiv", + "device-types": "Tipuri dispozitiv", + "name": "Nume", + "name-required": "Numele este obligatoriu", + "description": "Descriere", + "label": "Etichetă", + "events": "Evenimente", + "details": "Detalii", + "copyId": "Copie ID Dispozitiv", + "copyAccessToken": "Copiază Token Acces", + "idCopiedMessage": "ID dispozitiv copiat în clipboard", + "accessTokenCopiedMessage": "Token acces dispozitiv copiat în clipboard", + "assignedToCustomer": "Repartizat clientului", + "unable-delete-device-alias-title": "Pseudonimul dispozitivului nu poate fi șters", + "unable-delete-device-alias-text": "Pseudonimul dispozitivului '{{deviceAlias}}' nu poate fi șters, întrucât este folosit de widget(urile):
{{widgetsList}}", + "is-gateway": "Este gateway", + "public": "Public", + "device-public": "Dispozitivul este public", + "select-device": "Selectează Dispozitiv", + "import": "Importă Dispozitiv", + "device-file": "Fișier dispozitiv" + }, + "dialog": { + "close": "Închide Casetă Dialog" + }, + "direction": { + "column": "Coloană", + "row": "Linie" + }, + "error": { + "unable-to-connect": "Conexiunea cu serverul imposibilă! Ești conectat la Internet?", + "unhandled-error-code": "Cod eroare negestionată: {{errorCode}}", + "unknown-error": "Eroare necunoscută" + }, + "entity": { + "entity": "Entitate", + "entities": "Entități", + "aliases": "Pseudonime Entități", + "entity-alias": "Pseudonim Entitate", + "unable-delete-entity-alias-title": "Ștergerea pseudonimului entității este imposibilă", + "unable-delete-entity-alias-text": "Pseudonimul entității '{{entityAlias}}', fiind folosit de widgetul/widgeturile:
{{widgetsList}}", + "duplicate-alias-error": "Pseudonimul entității este duplicat
Pseudonimele entităților trebuie să fie unice, în același panou", + "missing-entity-filter-error": "Lipsă filtru pentru pseudonimul '{{alias}}'", + "configure-alias": "Configurează pseudonimul '{{alias}}'", + "alias": "Pseudonim", + "alias-required": "Pseudonimul entității este necesar", + "remove-alias": "șterge Alias Entitate", + "add-alias": "Adaugă Alias Entitate", + "entity-list": "Listă Entități", + "entity-type": "Tip Entitate", + "entity-types": "Tipuri Entități", + "entity-type-list": "Listă Tipuri Entități", + "any-entity": "Orice Entitate", + "enter-entity-type": "Introdu Tip Entitate", + "no-entities-matching": "Nu a fost găsită nicio entitate conținând '{{entity}}' ", + "no-entity-types-matching": "Nu au fost găsite tipuri de entități conținând '{{entityType}}'", + "name-starts-with": "Numele începe cu", + "use-entity-name-filter": "Folosește filtru", + "entity-list-empty": "Nicio entitate selectată", + "entity-type-list-empty": "Niciun tip entitate selectat", + "entity-name-filter-required": "Filtrul pentru nume entitate este necesar", + "entity-name-filter-no-entity-matched": "Nu a fost găsită nicio entitate al cărei nume începe cu '{{entity}}'", + "all-subtypes": "Toate", + "select-entities": "Selectează entități", + "no-aliases-found": "Niciun pseudonim găsit", + "no-alias-matching": "Nu am găsit pseudonimul '{{alias}}'", + "create-new-alias": "Creează unul nou!", + "key": "Cheie", + "key-name": "Nume Cheie", + "no-keys-found": "Nicio cheie găsită", + "no-key-matching": "Nu am găsit cheia '{{key}}'", + "create-new-key": "Creează una nouă!", + "type": "Tip", + "type-required": "Tipul entității este necesar", + "type-device": "Dispozitiv", + "type-devices": "Dispozitive", + "list-of-devices": "{ count, plural, 1 {un dispozitiv} other {Listă # dispozitive} }", + "device-name-starts-with": "Dispozitive a căror nume începe cu '{{prefix}}'", + "type-asset": "Proprietate", + "type-assets": "Proprietăți", + "list-of-assets": "{ count, plural, 1 {o proprietate} other {Listă # proprietăți} }", + "asset-name-starts-with": "Proprietate a cărei nume începe cu '{{prefix}}'", + "type-entity-view": "Entitate Definită", + "type-entity-views": "Entități Definite", + "list-of-entity-views": "{ count, plural, 1 {o entitate definită} other {Listă # entități definite} }", + "entity-view-name-starts-with": "Entități definite al căror nume începe cu '{{prefix}}'", + "type-rule": "Regulă", + "type-rules": "Reguli", + "list-of-rules": "{ count, plural, 1 {o regulă} other {Listă # reguli} }", + "rule-name-starts-with": "Reguli al căror nume începe cu '{{prefix}}'", + "type-plugin": "Plugin", + "type-plugins": "Plugin-uri", + "list-of-plugins": "{ count, plural, 1 {un plugin} other {Listă # plugin-uri} }", + "plugin-name-starts-with": "Plugin-uri al căror nume începe cu '{{prefix}}'", + "type-tenant": "Locatar", + "type-tenants": "Locatari", + "list-of-tenants": "{ count, plural, 1 {un locatar} other {Listă # locatari} }", + "tenant-name-starts-with": "Locatari al căror nume începe cu '{{prefix}}'", + "type-customer": "Client", + "type-customers": "Clienţi", + "list-of-customers": "{ count, plural, 1 {un client} other {Listă # Clienţi} }", + "customer-name-starts-with": "Clienţi al căror nume începe cu '{{prefix}}'", + "type-user": "Utilizator", + "type-users": "Utilizatori", + "list-of-users": "{ count, plural, 1 {un utilizator} other {Listă # utilizatori} }", + "user-name-starts-with": "Utilizatori al căror nume începe cu '{{prefix}}'", + "type-dashboard": "Panou Control", + "type-dashboards": "Panouri Control", + "list-of-dashboards": "{ count, plural, 1 {un panou control} other {Listă # panouri control} }", + "dashboard-name-starts-with": "Panouri control al căror nume începe cu '{{prefix}}'", + "type-alarm": "Alarmă", + "type-alarms": "Alarme", + "list-of-alarms": "{ count, plural, 1 {o alarmă} other {Listă # alarme} }", + "alarm-name-starts-with": "Alarme al căror nume începe cu '{{prefix}}'", + "type-rulechain": "Flux", + "type-rulechains": "Fluxuri", + "list-of-rulechains": "{ count, plural, 1 {un flux} other {Listă # fluxuri} }", + "rulechain-name-starts-with": "Fluxuri al căror nume începe cu '{{prefix}}'", + "type-rulenode": "Nod flux", + "type-rulenodes": "Noduri flux", + "list-of-rulenodes": "{ count, plural, 1 {un nod flux} other {Listă # noduri flux} }", + "rulenode-name-starts-with": "Noduri flux al căror nume începe cu '{{prefix}}'", + "type-current-customer": "Client Curent", + "search": "Caută Entități", + "selected-entities": "{ count, plural, 1 {o entitate} other {# entități} } selectate", + "entity-name": "Nume Entitate", + "entity-label": "Etichetă Entitate", + "details": "Detalii Entitate", + "no-entities-prompt": "Nu au fost găsite entități", + "no-data": "Nu există date de afișat", + "columns-to-display": "Coloane Afișate" + }, + "entity-field": { + "created-time": "Momentul Creării", + "name": "Denumire", + "type": "Tip", + "first-name": "Prenume", + "last-name": "Nume", + "email": "eMail", + "title": "Formula De Adresare", + "country": "Ţara", + "state": "Judeţ", + "city": "Oraş", + "address": "Adresa 1", + "address2": "Adresa 2", + "zip": "Cod Poştal", + "phone": "Telefon", + "label": "Etichetă" + }, + "entity-view": { + "entity-view": "Entitate Definită", + "entity-view-required": "Entitatea definită este obligatorie", + "entity-views": "Entități Definite", + "management": "Administrare Entități Definite", + "view-entity-views": "Afişează Entități Definite", + "entity-view-alias": "Pseudonim Entitate Definită", + "aliases": "Pseudonime Entități Definite", + "no-alias-matching": "'{{alias}}' Pseudonimul nu a fost găsit", + "no-aliases-found": "Nu au fost găsite pseudonime", + "no-key-matching": "Cheia '{{key}}' nu a fost găsită", + "no-keys-found": "Nu a fost găsită nicio cheie", + "create-new-alias": "Creează Alias Nou", + "create-new-key": "Creează Cheie Nouă", + "duplicate-alias-error": "Pseudonimul: '{{alias}}' este deja înregistrat
Pseudonimele pentru entităţi trebuie să fie unice în acelaşi panou", + "configure-alias": "Configurează pseudonim'{{alias}}'", + "no-entity-views-matching": "Nu au fost găsite entități definite după criteriul: '{{entity}}'", + "alias": "Pseudonim", + "alias-required": "Pseudonimul entității definite este obligatoriu", + "remove-alias": "Şterge pseudonim entitate definită", + "add-alias": "Adaugă pseudonim entitate definită", + "name-starts-with": "Numele entității definite începe cu ", + "entity-view-list": "Listă Entități Definite", + "use-entity-view-name-filter": "Filtrează", + "entity-view-list-empty": "Nu au fost selectate entități definite", + "entity-view-name-filter-required": "Filtrul pentru numele entității definite este obligatoriu", + "entity-view-name-filter-no-entity-view-matched": "Nu au fost găsite entități definite al căror nume conține: '{{entityView}}'", + "add": "Adaugă Entitate Definită", + "assign-to-customer": "Repartizare către client", + "assign-entity-view-to-customer": "Repartizare Entități Definite Clientului", + "assign-entity-view-to-customer-text": "Selectează entitățile definite ce vor fi repartizate clientului", + "no-entity-views-text": "Nu există entități definite", + "assign-to-customer-text": "Selectează clientul căruia îi vor fi repartizate entitățile definite", + "entity-view-details": "Detalii Entitate Definită", + "add-entity-view-text": "Adaugă Entitate Definită", + "delete": "ßterge Entitate Definită", + "assign-entity-views": "Repartizează Entitate Definită", + "assign-entity-views-text": "Repartizează { count, plural, 1 {o entitate definită} other {# entități definite} } clientului", + "delete-entity-views": "Şterge Entități Definite", + "unassign-from-customer": "Ştergere Repartizare Către Client", + "unassign-entity-views": "Ştergere Repartizare Entități Definite", + "unassign-entity-views-action-title": "Şterge repartizare { count, plural, 1 {o entitate definită} other {# entități definite} } de la client ", + "assign-new-entity-view": "Repartizează Entitate Definită Nouă", + "delete-entity-view-title": "Sigur vrei să ștergi entitatea definită : '{{entityViewName}}'?", + "delete-entity-view-text": "ATENŢIE! După confirmare, entitatea definită şi toate datele asociate cu aceasta vor fi șterse IREVERSIBIL!", + "delete-entity-views-title": "Sigur vrei să ștergi{ count, plural, 1 {o entitate definită} other {# entități definite} }?", + "delete-entity-views-action-title": "Şterge { count, plural, 1 {o entitate definită} other {# entități definite} }", + "delete-entity-views-text": "ATENŢIE! După confirmare, toate entitățile definite și datele asociate acestora vor fi șterse IREVERSIBIL!", + "unassign-entity-view-title": "Sigur vrei să ștergi repartizarea entității definite : '{{entityViewName}}'?", + "unassign-entity-view-text": "ATENŢIE! După confirmare, clientul nu va mai putea accesa entitatea definită selectată", + "unassign-entity-view": "Şterge Repartizare Entitate Definită", + "unassign-entity-views-title": "Sigur vrei să ștergi repartizarea a { count, plural, 1 {o entitate definită} other {# entități definite} }?", + "unassign-entity-views-text": "ATENŢIE! După confirmare, clientul nu va mai putea accesa entitățile definite selectate", + "entity-view-type": "Tip Entitate Definită", + "entity-view-type-required": "Tipul ecranului pentru entitate este obligatoriu", + "select-entity-view-type": "Selectaţi Tipul Ecranului Pentru Entitate", + "enter-entity-view-type": "Introduceţi Tipul Ecranului Pentru Entitate", + "any-entity-view": "Orice Entitate Definită", + "no-entity-view-types-matching": "Nu au fost găsite tipuri de entitate definită care să corespundă criteriului '{{entitySubtype}}'", + "entity-view-type-list-empty": "Nu au fost selectate tipuri de entitate definită", + "entity-view-types": "Tipuri Entitate Definită", + "name": "Denumire", + "name-required": "Denumirea este obligatorie", + "description": "Descriere", + "events": "Evenimente", + "details": "Detalii", + "copyId": "Copie ID Entitate Definită", + "assignedToCustomer": "Repartizat Clientului", + "unable-entity-view-device-alias-title": "Pseudonimul entității definite nu poate fi şters", + "unable-entity-view-device-alias-text": "Pseudonimul dispozitivului : '{{entityViewAlias}}' nu poate fi șters, fiind folosit de widgetul/widgeturile :
{{widgetsList}}", + "select-entity-view": "Selectează Entitate Definită", + "make-public": "Declară Entitate Definită Publică", + "make-private": "Declară Entitate Definită Privată", + "start-date": "Dată Început", + "start-ts": "Oră Început", + "end-date": "Dată Sfârșit", + "end-ts": "Oră Sfârșit", + "date-limits": "Limite Dată", + "client-attributes": "Atribute Client", + "shared-attributes": "Atribute Partajate", + "server-attributes": "Atribute Server", + "timeseries": "Serii Temporale", + "client-attributes-placeholder": "Atribute Client", + "shared-attributes-placeholder": "Atribute Partajate", + "server-attributes-placeholder": "Atribute Server", + "timeseries-placeholder": "Serii Temporale", + "target-entity": "Entitate Destinaţie", + "attributes-propagation": "Propagare Atribute", + "attributes-propagation-hint": "Entitatea definită va copia automat atributele specificate de la entitatea destinaţie, de fiecare dată când este salvată sau actualizată. Din motive de performanţă, atributele entităţii de destinaţie nu sunt propagate către entitatea definită la fiecare modificare de atribut. Poți activa propagarea automată a atributelor prin configurarea nodului în mod \"copiază pentru a vedea\" în fluxul tău și conectarea mesajelor \"Post Atribute\" și \"Atribute Actualizate\" la noul nod", + "timeseries-data": "Date Serii Temporale", + "timeseries-data-hint": "Configurează intervale timp pentru seriile temporale ale entităţii destinaţie, care vor fi accesibile prin entitatea definită. Acestea nu vor putea fi modificate", + "make-public-entity-view-title": "Sigur vrei să faci publică entitatea definită: '{{entityViewName}}'?", + "make-public-entity-view-text": "ATENŢIE! După confirmare, entitatea definită și datele aferente acesteia vor deveni publice, deci accesibile oricui", + "make-private-entity-view-title": "Sigur vrei să faci privată entitatea definită: '{{entityViewName}}'?", + "make-private-entity-view-text": "ATENŢIE! După confirmare, entitatea definită şi toate datele aferente acesteia, vor deveni private, la ele având acces doar proprietarul" + }, + "event": { + "event-type": "Tip Eveniment", + "type-error": "Eroare", + "type-lc-event": "Durată Eveniment", + "type-stats": "Statistică", + "type-debug-rule-node": "Depanare", + "type-debug-rule-chain": "Depanare", + "no-events-prompt": "Nu au fost găsite evenimente", + "error": "Eroare", + "alarm": "Alarmă", + "event-time": "Oră Eveniment", + "server": "Server", + "body": "Corp", + "method": "Metodă", + "type": "Tip", + "entity": "Entitate", + "message-id": "ID Mesaj", + "message-type": "Tip Mesaj", + "data-type": "Tip Date", + "relation-type": "Tip Relaţie", + "metadata": "Metadata", + "data": "Date", + "event": "Eveniment", + "status": "Stare", + "success": "Succes", + "failed": "Eşuat", + "messages-processed": "Mesaje procesate", + "errors-occurred": "Au apărut erori" + }, + "extension": { + "extensions": "Extensii", + "selected-extensions": "{ count, plural, 1 {o extensie selectată} other {# extensii selectate} }", + "type": "Tip", + "key": "Cheie", + "value": "Valoare", + "id": "ID", + "extension-id": "ID Extensie", + "extension-type": "Tip Extensie", + "transformer-json": "JSON *", + "unique-id-required": "ID-ul curent pentru extensie este deja înregistrat", + "delete": "Şterge Extensie", + "add": "Adaugă Extensie", + "edit": "Editează Extensie", + "delete-extension-title": "Sigur vrei să ștergi extensia: '{{extensionId}}'?", + "delete-extension-text": "ATENŢIE! După confirmare, extensia şi toate datele asociate acesteia, vor fi șterse IREVERSIBIL!", + "delete-extensions-title": "Sigur vrei să ștergi { count, plural, 1 {o extensie} other {# extensii} }?", + "delete-extensions-text": "ATENŢIE! După confirmare, toate extensiile selectate vor fi șterse IREVERSIBIL!", + "converters": "Convertoare", + "converter-id": "ID Convertoare", + "configuration": "Configurare", + "converter-configurations": "Configurator Convertoare", + "token": "Token De Securitate", + "add-converter": "Adaugă Convertor", + "add-config": "Adaugă Configurator Convertoare", + "device-name-expression": "Expresie Denumire Dispozitiv", + "device-type-expression": "Expresie Tip Dispozitiv", + "custom": "Definit De Utilizator", + "to-double": "To Double", + "transformer": "Transformare", + "json-required": "Este necesar transformer json", + "json-parse": "Analiză transformer json imposibilă", + "attributes": "Atribute", + "add-attribute": "Adaugă Atribut", + "add-map": "Adaugă Mapare", + "timeseries": "Date Cronologice", + "add-timeseries": "Adaugă Set Date Cronologice", + "field-required": "Câmpul Este Obligatoriu", + "brokers": "Brokeri", + "add-broker": "Adaugă Broker", + "host": "Gazdă", + "port": "Port", + "port-range": "Valoarea portului trebuie să fie în intervalul 1 - 65535", + "ssl": "SSL", + "credentials": "Acreditări", + "username": "Nume Utilizator", + "password": "Parolă", + "retry-interval": "Interval Reîncercare (milisecunde)", + "anonymous": "Anonim", + "basic": "Bazic", + "pem": "PEM", + "ca-cert": "Fişier Certificat CA", + "private-key": "Fişier Cheie Privată *", + "cert": "Fişier Certificat *", + "no-file": "Niciun fișier selectat", + "drop-file": "Trageţi un fişier sau selectaţi cu mouseul un fişier pentru a fi încărcat", + "mapping": "Mapare", + "topic-filter": "Filtru Topic", + "converter-type": "Tipul Convertor", + "converter-json": "Json", + "json-name-expression": "Expresie JSON Pentru Nume Dispozitiv", + "topic-name-expression": "Expresie TOPIC Pentru Nume Dispozitiv", + "json-type-expression": "Expresie JSON Pentru Tip Dispozitiv", + "topic-type-expression": "Expresie TOPIC Pentru Tip Dispozitiv", + "attribute-key-expression": "Expresie Cheie Atribut", + "attr-json-key-expression": "Expresie JSON Cheie Atribut", + "attr-topic-key-expression": "Expresie Topic Cheie Atribut", + "request-id-expression": "Expresie Cerere ID", + "request-id-json-expression": "Expresie JSON Cerere ID", + "request-id-topic-expression": "Expresie TOPIC Cerere ID", + "response-topic-expression": "Expresie Răspuns Topic", + "value-expression": "Valoare Expresie", + "topic": "Topic", + "timeout": "Timp De Expirare (milisecunde)", + "converter-json-required": "Convertorul JSON este obligatoriu", + "converter-json-parse": "Analiză converter JSON imposibilă", + "filter-expression": "Filtrează expresie", + "connect-requests": "Solicitări Conectare", + "add-connect-request": "Adaugă Solicitare Conectare", + "disconnect-requests": "Solicitări Deconectare", + "add-disconnect-request": "Adaugă Solicitare Deconectare", + "attribute-requests": "Solicitări Atribut", + "add-attribute-request": "Adaugă Solicitare Atribut", + "attribute-updates": "Actualizări Atribut", + "add-attribute-update": "Adaugă Actualizare Atribut", + "server-side-rpc": "Server RPC", + "add-server-side-rpc-request": "Adaugă Solicitare RPC Server-Side", + "device-name-filter": "Filtru Nume Dispozitiv", + "attribute-filter": "Filtru Atribut", + "method-filter": "Filtru Metodă", + "request-topic-expression": "Solicită expresie topic", + "response-timeout": "Timp răspuns în milisecunde", + "topic-expression": "Expresie Topic", + "client-scope": "Scop Client", + "add-device": "Adaugă Dispozitiv", + "opc-server": "Servere", + "opc-add-server": "Adaugă Server", + "opc-add-server-prompt": "Te rog, adaugă server ", + "opc-application-name": "Nume Aplicație", + "opc-application-uri": "URI Aplicație", + "opc-scan-period-in-seconds": "Perioadă Scanare (secunde)", + "opc-security": "Securitate", + "opc-identity": "Identitate", + "opc-keystore": "Keystore", + "opc-type": "Tip OPC", + "opc-keystore-type": "Tip Keystore", + "opc-keystore-location": "Localizare *", + "opc-keystore-password": "Parolă", + "opc-keystore-alias": "Pseudonim", + "opc-keystore-key-password": "Cheie Parolă", + "opc-device-node-pattern": "Structură Nod Dispozitiv", + "opc-device-name-pattern": "Structură Nume Dispozitiv", + "modbus-server": "Servere/sclavi", + "modbus-add-server": "Adaugă server/sclav", + "modbus-add-server-prompt": "Te rog adaugă server/sclav", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "Reconectare Automată", + "modbus-rtu-over-tcp": "RTU peste TCP", + "modbus-port-name": "Nume Port Serial", + "modbus-encoding": "Codificare", + "modbus-parity": "Paritate", + "modbus-baudrate": "Rată Baud", + "modbus-databits": "Biți Date", + "modbus-stopbits": "Biți Stop", + "modbus-databits-range": "Bitii de date trebuie să fie în intervalul 7-8", + "modbus-stopbits-range": "Bitii de stop trebuie să fie în intervalul 1-2", + "modbus-unit-id": "ID Unitate", + "modbus-unit-id-range": "ID-ul unității trebuie să fie în intervalul 1-247", + "modbus-device-name": "Nume Dispozitiv", + "modbus-poll-period": "Perioadă Interogare (ms)", + "modbus-attributes-poll-period": "Perioadă Interogare Atribute (ms)", + "modbus-timeseries-poll-period": "Perioadă Interogare serii temporale (ms)", + "modbus-poll-period-range": "Perioada de interogare trebuie să fie pozitivă", + "modbus-tag": "Tag", + "modbus-function": "Funcție", + "modbus-register-address": "Adresă Registru", + "modbus-register-address-range": "Adresa registrului trebuie să fie în intervalul 0-65535", + "modbus-register-bit-index": "Index biți", + "modbus-register-bit-index-range": "Indexul biților trebuie să fie în intervalul 0-15", + "modbus-register-count": "Număr Regiștri", + "modbus-register-count-range": "Numărul regiștrilor trebuie să fie pozitiv", + "modbus-byte-order": "Ordine Bytes", + "sync": { + "status": "Stare", + "sync": "Sincronizat", + "not-sync": "Nesincronizat", + "last-sync-time": "Ultima Sincronizare", + "not-available": "Indisponibil" + }, + "export-extensions-configuration": "Exportă configuraţie extensii", + "import-extensions-configuration": "Importă configuraţie extensii", + "import-extensions": "Importă Extensii", + "import-extension": "Importă Extensie", + "export-extension": "Exportă Extensie", + "file": "Fişier Extensii", + "invalid-file-error": "Fişier extensie invalid" + }, + "fullscreen": { + "expand": "Extinde Către Ecran Complet", + "exit": "Ieşire Din Ecran Complet", + "toggle": "Comutare Ecran Complet", + "fullscreen": "Ecran Complet" + }, + "function": { + "function": "Funcţie" + }, + "grid": { + "delete-item-title": "Sigur vrei să ștergi elementul?", + "delete-item-text": "ATENŢIE! După confirmare, elementul şi toate datele referitoare la acesta, vor fi șterse IREVERSIBIL!", + "delete-items-title": "Sigur vrei să ștergi { count, plural, 1 {un element} other {# elemente} }?", + "delete-items-action-title": "Şterge { count, plural, 1 {un element} other {# elemente} }", + "delete-items-text": "ATENŢIE! După confirmare, toate elementele selectate şi toate datele referitoare la acestea, vor fi șterse IREVERSIBIL!", + "add-item-text": "Adaugă Element Nou", + "no-items-text": "Nu Au Fost Găsite Elemente", + "item-details": "Detalii Element", + "delete-item": "Şterge Element", + "delete-items": "Şterge Elemente", + "scroll-to-top": "Derulare la începutul listei" + }, + "help": { + "goto-help-page": "Mergi la pagina de ajutor" + }, + "home": { + "home": "Acasă", + "profile": "Profil", + "logout": "Deconectare", + "menu": "Meniu", + "avatar": "Avatar", + "open-user-menu": "Deschide Meniu Utilizator" + }, + "import": { + "no-file": "Niciun fișier selectat", + "drop-file": "Trage un fişier de tip JSON sau selectează cu mausul un fişier de tip JSON pentru a fi încărcat", + "drop-file-csv": "Trage un fişier de tip CSV sau selectează cu mouse-ul un fişier de tip csv pentru a fi încărcat", + "column-value": "Valoare", + "column-title": "Denumire", + "column-example": "Exemplu valori date", + "column-key": "Cheie Atribut/Telemetrie", + "csv-delimiter": "Delimitator CSV", + "csv-first-line-header": "Prima linie conţine denumiri de coloane", + "csv-update-data": "Actualizare atribute/telemetrie", + "import-csv-number-columns-error": "Un fișier trebuie să conțină cel puțin două coloane", + "import-csv-invalid-format-error": "Format fișier incorect, linia: '{{line}}'", + "column-type": { + "name": "Denumire", + "type": "Tip", + "label": "Etichetă", + "column-type": "Tipul Coloanei", + "client-attribute": "Atribut Client", + "shared-attribute": "Atribut Partajat", + "server-attribute": "Atribut Server", + "timeseries": "Date Cronologice", + "entity-field": "Câmp Entitate", + "access-token": "Token De Acces" + }, + "stepper-text":{ + "select-file": "Selectează un fişier", + "configuration": "Importă configurație", + "column-type": "Selectează tipul de coloane", + "creat-entities": "Creează entităţi noi", + "done": "Terminat" + }, + "message": { + "create-entities": "{{count}} entităţi noi au fost create cu succes", + "update-entities": "{{count}} entităţi noi au fost actualizate cu succes", + "error-entities": "A intervenit o eroare la crearea a {{count}} entităţi" + } + }, + "item": { + "selected": "Selectat" + }, + "js-func": { + "no-return-error": "Funcţia trebuie să returneze o valoare", + "return-type-mismatch": "Funcţia trebuie să returneze o valoare de tip: '{{type}}'", + "tidy": "Tidy" + }, + "key-val": { + "key": "Cheie", + "value": "Valoare", + "remove-entry": "Șterge Intrare", + "add-entry": "Adaugă Intrare", + "no-data": "Nu sunt intrări" + }, + "layout": { + "layout": "Amplasament", + "manage": "Modifică Amplasamente", + "settings": "Configurare Amplasamente", + "color": "Culoare", + "main": "Principal", + "right": "Dreapta", + "select": "Alege Amplasament Țintă" + }, + "legend": { + "direction": "Direcţie Legendă", + "position": "Poziţie Legendă", + "show-max": "Afişează Valoare Maximă", + "show-min": "Afişează Valoare Minimă", + "show-avg": "Afişează Valoare Medie", + "show-total": "Afişează Valoare Totală", + "settings": "Setări Legendă", + "min": "Minim", + "max": "Maxim", + "avg": "Medie", + "total": "Total", + "comparison-time-ago": { + "days": "(Ziua Trecută (Ieri))", + "weeks": "(Săptamâna Trecută)", + "months": "(Luna Trecută)", + "years": "(Anul Trecut)" + } + }, + "login": { + "login": "Intră în Cont", + "request-password-reset": "Solicită Resetarea Parolei", + "reset-password": "Resetează Parolă", + "create-password": "Creează Parolă", + "passwords-mismatch-error": "Parola reintrodusă trebuie să fie identică!", + "password-again": "Rescrie Parola", + "sign-in": "Intră în Cont", + "username": "Nume Utilizator (Adresa De eMail)", + "remember-me": "Ține-mă minte!", + "forgot-password": "Ai Uitat Parola?", + "password-reset": "Resetează Parola", + "expired-password-reset-message": "Parola ta a expirat! Este necesară schimbarea acesteia", + "new-password": "Parolă nouă", + "new-password-again": "Verificare parolă nouă", + "password-link-sent-message": "Ți-am trimis pe eMail un link pentru resetarea parolei", + "email": "eMail" + }, + "position": { + "top": "Sus", + "bottom": "Jos", + "left": "Stânga", + "right": "Dreapta" + }, + "profile": { + "profile": "Profil", + "last-login-time": "Ultima Accesare", + "change-password": "Schimbă Parola", + "current-password": "Parola Actuală" + }, + "relation": { + "relations": "Relaţii", + "direction": "Direcţie", + "search-direction": { + "FROM": "Dinspre", + "TO": "Înspre" + }, + "direction-type": { + "FROM": "Dinspre", + "TO": "Către" + }, + "from-relations": "Ieșire", + "to-relations": "Intrare", + "selected-relations": "{ count, plural, 1 {o relaţie selectată } other {# relaţii selectate } }", + "type": "Tip", + "to-entity-type": "Către Tip Entitate", + "to-entity-name": "Către Nume Entitate", + "from-entity-type": "Dinspre Tip Entitate", + "from-entity-name": "Dinspre Nume Entitate", + "to-entity": "Către Entitate", + "from-entity": "Dinspre Entitate", + "delete": "Şterge Relaţie", + "relation-type": "Tip Relaţie", + "relation-type-required": "Tipul relației este obligatoriu", + "any-relation-type": "Orice Tip", + "add": "Adaugă Relaţie", + "edit": "Şterge Relaţie", + "delete-to-relation-title": "Sigur vrei să ștergi relația către entitatea '{{entityName}}'?", + "delete-to-relation-text": "ATENŢIE! După confirmare,relaţia către entitatea '{{entityName}}' va fi ştearsă", + "delete-to-relations-title": "Sigur vrei să ștergi { count, plural, 1 {o relaţie} other {# relaţii} }?", + "delete-to-relations-text": "ATENŢIE! După confirmare, relaţiile selectate către entităţile corespondente și toate referirile la acestea, vor fi șterse IREVERSIBIL!", + "delete-from-relation-title": "Sigur vrei să ștergi relația dinspre entitatea '{{entityName}}'?", + "delete-from-relation-text": "ATENŢIE! După confirmare,relaţia dinspre entitatea '{{entityName}}' va fi ștearsă", + "delete-from-relations-title": "Sigur vrei să ștergi { count, plural, 1 {o relaţie} other {# relaţii} }?", + "delete-from-relations-text": "ATENŢIE! După confirmare, relaţiile selectate către entităţile corespondente și toate referirile la acestea, vor fi șterse IREVERSIBIL!", + "remove-relation-filter": "Elimină Filtru Relaţie", + "add-relation-filter": "Adaugă Filtru Relaţie", + "any-relation": "Orice Relaţie", + "relation-filters": "Filtre Relaţie", + "additional-info": "Informaţii Adiţionale (JSON)", + "invalid-additional-info": "Informaţiile adiţionale (JSON) nu au putut fi analizate" + }, + "rulechain": { + "rulechain": "Flux", + "rulechains": "Fluxuri", + "root": "Origine", + "delete": "Şterge Fluxuri", + "name": "Denumire", + "name-required": "Denumirea este obligatorie", + "description": "Descriere", + "add": "Adaugă Fluxuri", + "set-root": "Stabileşte Originea Fluxurilor", + "set-root-rulechain-title": "Sigur vrei să setezi '{{ruleChainName}}' ca rădăcină?", + "set-root-rulechain-text": "ATENŢIE! După confirmare, fluxul va deveni rădăcină şi va gestiona toate mesajele de intrare", + "delete-rulechain-title": "Sigur vrei să ștergi fluxul '{{ruleChainName}}'?", + "delete-rulechain-text": "ATENŢIE! După confirmare, fluxul şi toate datele referitoare la acesta, vor fi șterse IREVERSIBIL!", + "delete-rulechains-title": "Sigur vrei să ștergi { count, plural, 1 {un flux} other {# fluxuri} }?", + "delete-rulechains-action-title": "Ştergi { count, plural, 1 {un flux} other {# fluxuri} }", + "delete-rulechains-text": "ATENŢIE! După confirmare, fluxul şi toate datele referitoare la acesta, vor fi șterse IREVERSIBIL!", + "add-rulechain-text": "Adaugă Flux Nou", + "no-rulechains-text": "Nu au fost găsite fluxuri", + "rulechain-details": "Detalii Flux", + "details": "Detalii", + "events": "Evenimente", + "system": "Sistem", + "import": "Importă Flux", + "export": "Exportă Flux", + "export-failed-error": "Fluxul nu poate fi exportat; {{error}}", + "create-new-rulechain": "Creează Flux Nou", + "rulechain-file": "Fişierul Flux", + "invalid-rulechain-file-error": "Fluxul nu poate fi importat; structură date invalidă", + "copyId": "Copiază ID Flux", + "idCopiedMessage": "ID Flux copiat în clipboard", + "select-rulechain": "Selectează Flux", + "no-rulechains-matching": "Nu au fost găsite fluxuri după criteriul: '{{entity}}'", + "rulechain-required": "Fluxul este obligatoriu", + "management": "Administrare Fluxuri", + "debug-mode": "Mod Depanare" + }, + "rulenode": { + "details": "Detalii", + "events": "Evenimente", + "search": "Noduri Căutare", + "open-node-library": "Bibliotecă Noduri", + "add": "Adaugă Regulă Nod", + "name": "Denumire", + "name-required": "Denumirea este obligatorie", + "type": "Tip", + "description": "Descriere", + "delete": "Şterge Regulă Nod", + "select-all-objects": "Selectează Toate Nodurile Şi Conexiunile", + "deselect-all-objects": "Deselectează Toate Nodurile Şi Conexiunile", + "delete-selected-objects": "Şterge Toate Nodurile Şi Conexiunile", + "delete-selected": "Şterge Selecţia", + "select-all": "Selectează Tot", + "copy-selected": "Copiază Selecţia", + "deselect-all": "Deselectează Tot", + "rulenode-details": "Detalii Regulă Nod", + "debug-mode": "Mod Depanare", + "configuration": "Configurare", + "link": "Link", + "link-details": "Detalii Link Regulă Nod", + "add-link": "Adaugă Link", + "link-label": "Etichetă Link", + "link-label-required": "Eticheta link-ului este obligatorie", + "custom-link-label": "Etichetă Link Definit de Utilizator", + "custom-link-label-required": "Eticheta pentru link, definită de către utilizator, este obligatorie", + "link-labels": "Etichete Link-uri)", + "link-labels-required": "Etichetele link-urilor sunt obligatorii", + "no-link-labels-found": "Nu au fost găsite etichete pentru link", + "no-link-label-matching": "Eticheta : '{{label}}' nu a fost găsită", + "create-new-link-label": "Creează Etichetă Nouă", + "type-filter": "Filtrează", + "type-filter-details": "Filtrează mesajele de intrare după condiţiile configurate", + "type-enrichment": "Îmbogățire", + "type-enrichment-details": "Adaugă informaţii adiţionale in metadata mesajului", + "type-transformation": "Transformare", + "type-transformation-details": "Schimbă payload şi metadata mesajului", + "type-action": "Acţiune", + "type-action-details": "Execută o acţiune specială", + "type-external": "Extern", + "type-external-details": "Interacţiuni cu sisteme externe", + "type-rule-chain": "Flux", + "type-rule-chain-details": "Transmite mesajele de intrare către fluxul specificat", + "type-input": "Intrare", + "type-input-details": "Intrarea logică pentru flux, transmite mesajele de intrare către următoarea regulă de nod înrudită", + "type-unknown": "Necunoscut", + "type-unknown-details": "Detalii regulă nod necunoscute", + "directive-is-not-loaded": "Configurarea definită pentru directiva : '{{directiveName}}' nu este disponibilă", + "ui-resources-load-error": "Eroare la încărcarea configurării resurselor UI", + "invalid-target-rulechain": "Fluxul Destinaţie nu a fost găsit", + "test-script-function": "Funcţie Test Script", + "message": "Mesaj", + "message-type": "Tip Mesaj", + "select-message-type": "Selectează Tipul Mesajului", + "message-type-required": "Tipul mesajului este obligatoriu", + "metadata": "Metadata", + "metadata-required": "Intrările metadata nu pot fi vide", + "output": "Ieşire", + "test": "Test", + "help": "Ajutor", + "reset-debug-mode": "Dezactivează modul depanare în toate nodurile" + }, + "tenant": { + "tenant": "Locatar", + "tenants": "Locatari", + "management": "Administrare Locatar", + "add": "Adaugă Locatar", + "admins": "Administratori", + "manage-tenant-admins": "Gestionare Administratori Locatar", + "delete": "Şterge Locatar", + "add-tenant-text": "Adaugă Locatar Nou", + "no-tenants-text": "Nu au fost găsiţi locatari", + "tenant-details": "Detalii Locatar", + "delete-tenant-title": "Sigur vrei să ștergi locatarul: '{{tenantTitle}}'?", + "delete-tenant-text": "ATENŢIE! După confirmare, locatarul şi toate datele referitoare la acesta, vor fi șterse IREVERSIBIL!", + "delete-tenants-title": "Sigur vrei să ștergi { count, plural, 1 {un locatar} other {# locatari} } ?", + "delete-tenants-action-title": "Şterge { count, plural, 1 {un locatar} other {# locatari} }", + "delete-tenants-text": "ATENŢIE! După confirmare, locatarii selectaţi şi datele aferente acestora, vor fi șterse IREVERSIBIL!", + "title": "Titlu", + "title-required": "Titlul este obligatoriu", + "description": "Descriere", + "details": "Detalii", + "events": "Evenimente", + "copyId": "Copiază ID Locatar", + "idCopiedMessage": "ID Locatar a fost copiat în clipboard", + "select-tenant": "Selectează locatar", + "no-tenants-matching": "Nu au fost găsiţi locatari după criteriul: '{{entity}}'", + "tenant-required": "Locatarul este obligatoriu" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {o secundă} other {# secunde} }", + "minutes-interval": "{ minutes, plural, 1 {un minut} other {# minute} }", + "hours-interval": "{ hours, plural, 1 {o oră} other {# ore} }", + "days-interval": "{ days, plural, 1 {o zi} other {# zile} }", + "days": "Zile", + "hours": "Ore", + "minutes": "Minute", + "seconds": "Secunde", + "advanced": "Personalizat" + }, + "timewindow": { + "days": "{ days, plural, 1 {o zi} other {# zile} }", + "hours": "{ hours, plural, 1 {o oră} other {# ore} }", + "minutes": "{ minutes, plural, 1 {un minut} other {# minute} }", + "seconds": "{ seconds, plural, 1 {o secundă} other {# secunde} }", + "realtime": "Timp Real", + "history": "Istoric", + "last-prefix": "Interval:", + "period": "Început {{ startTime}} Sfârșit {{endTime}}", + "edit": "Editează Interval", + "date-range": "Interval Date", + "last": "Ultima/Ultimele", + "time-period": "Interval:", + "hide": "Ascunde" + }, + "user": { + "user": "Utilizator", + "users": "Utilizatori", + "customer-users": "Utilizatori Client", + "tenant-admins": "Administratori Locatar", + "sys-admin": "Administratori Sistem", + "tenant-admin": "Administrator Locatar", + "customer": "Clienţi", + "anonymous": "Anonim", + "add": "Adaugă Utilizator", + "delete": "Şterge Utilizator", + "add-user-text": "Adaugă Utilizator Nou", + "no-users-text": "Nu Există Utilizatori", + "user-details": "Detalii Utilizator", + "delete-user-title": "Sigur vrei să ștergi utilizatorul '{{userEmail}}'?", + "delete-user-text": "ATENŢIE! După confirmare, utilizatorul şi toate datele aferente acestuia, vor fi șterse IREVERSIBIL!", + "delete-users-title": "Sigur vrei să ștergi { count, plural, 1 {un utilizator} other {# utilizatori} }?", + "delete-users-action-title": "Ştergere { count, plural, 1 {un utilizator} other {# utilizatori} }", + "delete-users-text": "ATENŢIE! După confirmare, toţi utilizatorii selectaţi împreună cu datele aferente acestora, vor fi șterse IREVERSIBIL!", + "activation-email-sent-message": "Mesajul eMail pentru activare a fost trimis cu succes!", + "resend-activation": "Retrimite mesaj eMail de activare", + "email": "Adresă eMail", + "email-required": "Adresa eMail este obligatorie", + "invalid-email-format": "Adresa eMail este incorectă", + "first-name": "Prenume", + "last-name": "Nume", + "description": "Descriere", + "default-dashboard": "Panou Implicit", + "always-fullscreen": "Permanent Ecran Complet", + "select-user": "Selectează Utilizator", + "no-users-matching": "Nu există utilizatori care corespund criteriului: '{{entity}}'", + "user-required": "Utilizatorul este obligatoriu", + "activation-method": "Metoda De Activare", + "display-activation-link": "Afişează link activare", + "send-activation-mail": "Trimite mesaj eMail pentru activare", + "activation-link": "Link activare utilizator:", + "activation-link-text": "Pentru activarea contului, folosiți link: ", + "copy-activation-link": "Copiază link activare", + "activation-link-copied-message": "Link-ul de activare utilizator a fost copiat în clipboard", + "details": "Detalii", + "login-as-tenant-admin": "Acces ca locatar administrator", + "login-as-customer-user": "Acces ca utilizator client", + "disable-account": "Dezactivează cont utilizator", + "enable-account": "Activează cont utilizator", + "enable-account-message": "Cont utilizator activat!", + "disable-account-message": "Cont utilizator dezactivat!" + }, + "value": { + "type": "Tip Valoare", + "string": "Şir Caractere", + "string-value": "Valoare Şir Caractere", + "integer": "Număr Întreg", + "integer-value": "Valoare Număr Întreg", + "invalid-integer-value": "Valoare număr întreg incorectă", + "double": "Tip Double", + "double-value": "Valoare Tip Double", + "boolean": "Tip Bool: ", + "boolean-value": "Valoare Bool", + "false": "Fals", + "true": "Adevărat", + "long": "Tip Long" + }, + "widget": { + "widget-library": "Biblioteci Widgets", + "widget-bundle": "Pachete Widgets", + "select-widgets-bundle": "Selectează Pachete Widgets", + "management": "Administrare Widgets", + "editor": "Editor Widget", + "widget-type-not-found": "Eroare la încărcarea configuraţiei widgetului.
Probabil Asocierea \n cu tipul de widget a fost eliminată", + "widget-type-load-error": "Widgetul nu a fost încărcat din cauza următoarelor erori:", + "remove": "Elimină Widget", + "edit": "Editează Widget", + "remove-widget-title": "Sigur vrei să ștergi widgetul '{{widgetTitle}}'?", + "remove-widget-text": "ATENŢIE! După confirmare, widgetul şi toate datele aferente acestuia, vor fi șterse IREVERSIBIL!", + "timeseries": "Serii Temporale", + "search-data": "Caută Date", + "no-data-found": "Nu Au Fost Găsite Date", + "latest-values": "Ultimele Valori", + "rpc": "Widget Control ", + "alarm": "Widget Alarmă", + "static": "Widget Static", + "select-widget-type": "Selectaţi Tip Widget", + "missing-widget-title-error": "Titlul widgetului trebuie specificat!", + "widget-saved": "Widget Salvat", + "unable-to-save-widget-error": "Widgetul conține erori și nu poate fi salvat!", + "save": "Salvează Widget", + "saveAs": "Salvează Widget Ca...", + "save-widget-type-as": "Salvează Tip Widget Ca...", + "save-widget-type-as-text": "Introduceţi titlu nou widget şi/sau selectați pachet widget destinație", + "toggle-fullscreen": "Comută Ecran Complet", + "run": "Execută Widget", + "title": "Titlu Widget", + "title-required": "Titlul widgetului este obligatoriu", + "type": "Tip Widget", + "resources": "Resurse", + "resource-url": "JavaScript/CSS URL", + "remove-resource": "Şterge Resursă", + "add-resource": "Adaugă Resursă", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Schemă setări", + "datakey-settings-schema": "Schemă setări chei date", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "Sigur vrei să ștergi tip widget '{{widgetName}}'?", + "remove-widget-type-text": "ATENŢIE! După confirmare, tipul de widget şi toate datele aferente acestuia, vor fi șterse IREVERSIBIL!", + "remove-widget-type": "Şterge Tip Widget", + "add-widget-type": "Adaugă Tip Nou Widget", + "widget-type-load-failed-error": "Eroare încărcare tip widget!", + "widget-template-load-failed-error": "Eroare încarcare şablon widget!", + "add": "Adaugă Widget Nou", + "undo": "Anulează Modificări Widget", + "export": "Exportă Widget" + }, + "widget-action": { + "header-button": "Buton Principal Widget", + "open-dashboard-state": "Deschide Altă Stare a Panoului", + "update-dashboard-state": "Actualizează Starea Curentă A Panoului", + "open-dashboard": "Comută Către Alt Panou", + "custom": "Acţiuni Utilizator", + "custom-pretty": "Acţiuni Utilizator (cu şablon HTML)", + "target-dashboard-state": "Stare Panou Destinaţie", + "target-dashboard-state-required": "Starea panoului de destinaţie este obligatorie!", + "set-entity-from-widget": "Setează Entitate din Widget", + "target-dashboard": "Panou Destinaţie", + "open-right-layout": "Deschide Aspect Corect Al Panoului (accesare de pe mobil)" + }, + "widgets-bundle": { + "current": "Pachet Curent Widgeturi", + "widgets-bundles": "Pachete Widgeturi", + "add": "Adăugare Pachete Widgeturi", + "delete": "Ştergere Pachete Widgeturi", + "title": "Titlu", + "title-required": "Titlul este obligatoriu", + "add-widgets-bundle-text": "Adaugă pachet nou widgeturi", + "no-widgets-bundles-text": "Nu există pachete widgeturi", + "empty": "Pachetul de widgeturi este gol", + "details": "Detalii", + "widgets-bundle-details": "Detalii Pachet Widgeturi", + "delete-widgets-bundle-title": "Sigur vrei să ștergi pachetul de widgeturi '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "ATENŢIE! După Confirmare, pachetul de widgeturi şi toate datele aferente acestuia, vor fi șterse IREVERSIBIL!", + "delete-widgets-bundles-title": "Sigur vrei să ștergi { count, plural, 1 {un pachet widgeturi} other {# pachete widgeturi} }?", + "delete-widgets-bundles-action-title": "Şterge { count, plural, 1 {un packet widgeturi} other {# pachete widgeturi} }", + "delete-widgets-bundles-text": "ATENŢIE! După confirmare, toate pachetele selectate de widget-uri şi datele aferente acestuia, vor fi șterse IREVERSIBIL!", + "no-widgets-bundles-matching": "Nu au fost găsite pachete de widgeturi conținând textul '{{widgetsBundle}}' ", + "widgets-bundle-required": "Denumirea pachetelor de widgeturi este obligatorie", + "system": "Sistem", + "import": "Importă Pachet Widgeturi", + "export": "Exportă Pachet Widgeturi", + "export-failed-error": "Export pachet widgeturi imposibil: {{error}}", + "create-new-widgets-bundle": "Definire Pachet Widgeturi Nou", + "widgets-bundle-file": "Alege fișier pachet widgeturi", + "invalid-widgets-bundle-file-error": "Export pachet widgeturi imposibil; Structură date invalidă" + }, + "widget-config": { + "data": "Date", + "settings": "Setări", + "advanced": "Setări Avansate", + "title": "Titlu", + "title-tooltip": "Mesaj Detalii Titlu", + "general-settings": "Setări Generale", + "display-title": "Titlu Afişat", + "drop-shadow": "Cu Umbră", + "enable-fullscreen": "Permite Ecran Complet", + "background-color": "Culoare Fundal", + "text-color": "Culoare Text", + "padding": "Margine Interioară", + "margin": "Margine Exterioară", + "widget-style": "Stil Widget", + "title-style": "Stil Titlu", + "mobile-mode-settings": "Setări Afișare Mobil", + "order": "Ordine", + "height": "Înăltime", + "units": "Unitate măsură", + "decimals": "Număr Zecimale", + "timewindow": "Interval Timp", + "use-dashboard-timewindow": "Folosire Interval Timp Panou", + "display-timewindow": "Afişare Interval Timp", + "display-legend": "Afişare Legendă", + "datasources": "Surse Date", + "maximum-datasources": "Maximum { count, plural, 1 {o sursă date permisă} other {# surse date permise} }", + "datasource-type": "Tip", + "datasource-parameters": "Parametri", + "remove-datasource": "Elimină Sursă Date", + "add-datasource": "Adaugă Sursă Date", + "target-device": "Dispozitiv Destinaţie", + "alarm-source": "Sursă Alarmă", + "actions": "Acţiuni", + "action": "Acţiune", + "add-action": "Adaugă Acţiune", + "search-actions": "Caută Acţiuni", + "action-source": "Sursa Acțiunii", + "action-source-required": "Sursa acțiunii este obligatorie", + "action-name": "Numele Acțiunii", + "action-name-required": "Numele acțiunii este obligatoriu", + "action-name-not-unique": "O acţiune cu acelaşi nume este deja definită
Numele definit al acțiunii trebuie să fie unic in aceeaşi sursă de date", + "action-icon": "Pictogramă", + "action-type": "Tipul", + "action-type-required": "Tipul acțiunii este obligatoriu", + "edit-action": "Editare Acţiune", + "delete-action": "Ştergere Acţiune", + "delete-action-title": "Şterge acţiunea ", + "delete-action-text": "Ești sigur că vrei să ștergi acţiunea '{{actionName}}'?", + "display-icon": "Afişează Pictograma Titlului", + "icon-color": "Culoare Pictogramă", + "icon-size": "Mărime Pictogramă" + }, + "widget-type": { + "import": "Import Tip Widget", + "export": "Export Tip Widget", + "export-failed-error": "Eroare! Export imposibil pentru tip widget: {{error}}", + "create-new-widget-type": "Defineşte tip widget nou", + "widget-type-file": "Alege fişier pentru tip widget", + "invalid-widget-type-file-error": "Eroare! Tip widget nu poate fi importat; structură de date invalidă" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "D", + "Mon": "L", + "Tue": "M", + "Wed": "M", + "Thu": "J", + "Fri": "V", + "Sat": "S", + "Jan": "Ian", + "Feb": "Feb", + "Mar": "Mar", + "Apr": "Apr", + "May": "Mai", + "Jun": "Iun", + "Jul": "Iul", + "Aug": "Aug", + "Sep": "Sep", + "Oct": "Oct", + "Nov": "Nov", + "Dec": "Dec", + "January": "Ianuarie", + "February": "Februarie", + "March": "Martie", + "April": "Aprilie", + "Maz": "Mai", + "June": "Iunie", + "July": "Iulie", + "August": "August", + "September": "Septembrie", + "October": "Octombrie", + "November": "Noiembrie", + "December": "Decembrie", + "Custom Date Range": "Interval Date Personalizat", + "Date Range Template": "Șablon Interval Date", + "Today": "Astăzi", + "Yesterday": "Ieri", + "This Week": "Săptămâna Aceasta", + "Last Week": "Săptămâna Trecută", + "This Month": "Luna Aceasta", + "Last Month": "Luna Trecută", + "Year": "Anul", + "This Year": "Anul Acesta", + "Last Year": "Anul Trecut", + "Date picker": "Alege Data", + "Hour": "Oră", + "Day": "Zi", + "Week": "Săptămână", + "2 weeks": "2 săptămâni", + "Month": "Lună", + "3 months": "3 luni", + "6 months": "6 luni", + "Custom interval": "Interval Personalizat", + "Interval": "Interval:", + "Step size": "Pas", + "Ok": "Ok" + } + }, + "input-widgets": { + "attribute-not-allowed": "Acest widget nu poate folosi atributul specificat", + "blocked-location": "Acest browser blochează localizarea", + "claim-device": "Revendică Dispozitiv", + "claim-failed": "Încercarea revendicare dispozitiv eșuată", + "claim-not-found": "Dispozitivul nu a fost găsit", + "claim-successful": "Dispozitivul a fost revendicat cu succes", + "date": "Data", + "device-name": "Nume Dispozitiv", + "device-name-required": "Numele dispozitivului este obligatoriu", + "discard-changes": "Anulare Modificări", + "entity-attribute-required": "Atributul entităţii este obligatoriu", + "entity-coordinate-required": "Atât latitudinea Şi longitudinea sunt obligatorii", + "entity-timeseries-required": "Seriile temporale pentru entitate sînt obligatorii", + "get-location": "Află locaţia GPS actuală", + "latitude": "Latitudine", + "longitude": "Longitudine", + "not-allowed-entity": "Entitatea selectată nu poate avea atribute partajate", + "no-attribute-selected": "Niciun Atribut Selectat", + "no-datakey-selected": "Nicio cheie selectată", + "no-coordinate-specified": "Cheie date latitude/longitude nespecificată", + "no-entity-selected": "Nicio entitate selectată", + "no-image": "Lipsă Imagine", + "no-support-geolocation": "Acest browser nu permite geolocalizarea", + "no-support-web-camera": "Cameră Web nesuportată", + "no-timeseries-selected": "Serii temporale nespecificate", + "secret-key": "Cheie Secretă", + "secret-key-required": "Cheia secretă este obligatorie", + "switch-attribute-value": "Schimbă valoare atribut entitate", + "switch-camera": "Schimbă Camera", + "switch-timeseries-value": "Schimbă valori serii temporale entitate", + "take-photo": "Captură Imagine", + "time": "Timp", + "timeseries-not-allowed": "Parametrul nu este compatibil cu acest widget", + "update-failed": "Actualizare eșuată", + "update-successful": "Actualizare reușită", + "update-attribute": "Actualizare Atribut", + "update-timeseries": "Actualizare Serii Temporale", + "value": "Valoare" + } + }, + "icon": { + "icon": "Pictogramă", + "select-icon": "Selectează Pictogramă", + "material-icons": "Material Pictogramă", + "show-all": "Afişează Toate Pictogramele" + }, + "custom": { + "widget-action": { + "action-cell-button": "Acțiune buton celulă", + "row-click": "Eveniment : Click pe linie tabel", + "polygon-click": "Eveniment : Click pe poligon", + "marker-click": "Eveniment : Click pe marker", + "tooltip-tag-action": "Acţiune marcaj detalii mesaj ", + "node-selected": "Eveniment : Nod Selectat", + "element-click": "eveniment : click Pe element HTML", + "pie-slice-click": "Eveniment : Click pe sector cerc", + "row-double-click": "Eveniment : Dublu click pe linia tabelului" + } + }, + "language": { + "language": "Limba", + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + } + } +} From a97ed654170105efbb2e11c9fb54a07a5df1e5a0 Mon Sep 17 00:00:00 2001 From: fumil Date: Wed, 5 Feb 2020 23:15:12 +0200 Subject: [PATCH 185/261] Added Romanian and Latvian to "locales"; changed "locales" as to match each language spelling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "locales": { "de_DE": "Deutsch", "fr_FR": "Français", "zh_CN": "简体中文", "zh_TW": "繁體中文", "en_US": "English", "it_IT": "Italiano", "ko_KR": "한글", "ru_RU": "Русский", "es_ES": "Español", "ja_JA": "日本語", "tr_TR": "Türkçe", "fa_IR": "فارسي", "uk_UA": "Українська", "cs_CZ": "Česky", "el_GR": "Ελληνικά", "ro_RO": "Română", "lv_LV": "Latviešu" } --- ui/src/app/locale/locale.constant-cs_CZ.json | 28 +++--- ui/src/app/locale/locale.constant-de_DE.json | 36 ++++---- ui/src/app/locale/locale.constant-el_GR.json | 37 ++++---- ui/src/app/locale/locale.constant-en_US.json | 89 +++++--------------- ui/src/app/locale/locale.constant-es_ES.json | 28 +++--- ui/src/app/locale/locale.constant-fa_IR.json | 30 ++++--- ui/src/app/locale/locale.constant-fr_FR.json | 32 +++---- ui/src/app/locale/locale.constant-it_IT.json | 30 ++++--- ui/src/app/locale/locale.constant-ja_JA.json | 36 ++++---- ui/src/app/locale/locale.constant-ko_KR.json | 30 ++++--- ui/src/app/locale/locale.constant-lv_LV.json | 30 ++++--- ui/src/app/locale/locale.constant-ru_RU.json | 30 ++++--- ui/src/app/locale/locale.constant-tr_TR.json | 32 +++---- ui/src/app/locale/locale.constant-uk_UA.json | 30 ++++--- ui/src/app/locale/locale.constant-zh_CN.json | 29 ++++--- ui/src/app/locale/locale.constant-zh_TW.json | 35 ++++---- 16 files changed, 275 insertions(+), 287 deletions(-) diff --git a/ui/src/app/locale/locale.constant-cs_CZ.json b/ui/src/app/locale/locale.constant-cs_CZ.json index 8e8a6b8a2f..d3fc57cbe7 100644 --- a/ui/src/app/locale/locale.constant-cs_CZ.json +++ b/ui/src/app/locale/locale.constant-cs_CZ.json @@ -1639,21 +1639,23 @@ "language": { "language": "Jazyk", "locales": { - "de_DE": "German", - "fr_FR": "French", - "zh_CN": "Chinese", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", "en_US": "English", - "it_IT": "Italian", - "ko_KR": "Korean", - "ru_RU": "Russian", - "es_ES": "Spanish", - "ja_JA": "Japanese", - "tr_TR": "Turkish", - "fa_IR": "Persian", - "uk_UA": "Ukrainian", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", "cs_CZ": "Česky", - "el_GR": "Řečtina", - "lv_LV": "Lotyština" + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index d5f43f4ef4..c686ab6415 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -1686,22 +1686,24 @@ }, "language": { "language": "Sprache", - "locales": { - "de_DE": "Deutsch", - "fr_FR": "Französisch", - "zh_CN": "Chinesisch", - "en_US": "Englisch", - "it_IT": "Italienisch", - "ko_KR": "Koreanisch", - "ru_RU": "Russisch", - "es_ES": "Spanisch", - "ja_JA": "Japanisch", - "tr_TR": "Türkisch", - "fa_IR": "Persisch", - "uk_UA": "Ukrainisch", - "cs_CZ": "Tschechisch", - "el_GR": "Griechisch", - "lv_LV": "Lettisch" - } + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + } } } diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index 9b9783a9ad..a8ec2520fa 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -2601,23 +2601,24 @@ } }, "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": "Ελληνικά", - "lv_LV": "Λετονικά" - } + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + } } } diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 5acd0f81e5..68899394ff 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -50,8 +50,7 @@ "export": "Export", "share-via": "Share via {{provider}}", "continue": "Continue", - "discard-changes": "Discard Changes", - "download": "Download" + "discard-changes": "Discard Changes" }, "aggregation": { "aggregation": "Aggregation", @@ -1125,58 +1124,6 @@ "function": { "function": "Function" }, - "gateway": { - "key": "Key configuration", - "value": "Value configuration", - "remove-entry": "Remove configuration", - "add-entry": "Add configuration", - "no-data": "No configurations", - "gateway-required": "Gateway is required.", - "gateway-name": "Gateway name", - "create-new-gateway": "Create a new gateway", - "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?", - "no-gateway-matching": " '{{item}}' not found.", - "thingsboard": "ThingsBoard", - "connectors": "Connectors configuration", - "thingsboard-host": "ThingsBoard Host", - "thingsboard-port": "ThingsBoard Port", - "security-type": "Security type", - "tls-path-ca-certificate": "Path to CA certificate on gateway:", - "tls-path-private-key": "Path to private key on gateway:", - "tls-path-client-certificate": "Path to client certificate on gateway:", - "storage": "Storage", - "storage-type": "Storage type", - "storage-read-time": "Read records per time:", - "storage-max-time": "Maximum records per time:", - "storage-max-files": "Maximum files:", - "storage-data-path": "Data folder path:", - "download-tip": "Download configuration file", - "save-tip": "Save configuration file", - "remote-tip": "Allow remote configuration", - "remote": "Remote configuration", - "remote-logging-level": "Logging level", - "remote-logging-path-logs": "Path to logs", - "connector-type": "Connector type", - "update-config": "Add/update config JSON", - "delete": "Delete configuration", - "title-connectors-json": "Connector {{typeName}} configuration", - "json-required": "Config json is required for gateway config.", - "json-parse": "Unable to parse config json for gateway config.", - "tidy": "Tidy", - "tidy-tip": "Tidy config JSON", - "transformer-json-config": "JSON for the config*", - "toggle-fullscreen": "Toggle fullscreen", - "add-connectors": "Add new connectors", - "no-connectors": "No connectors", - "enabled": "Enabled", - "name": "Name", - "no-gateway-found": "No gateway found.", - "gateway": "Gateway", - "keyval-save-err": "Save config error", - "keyval-name-err": "Please add Name", - "keyval-type-err": "Please add Connector type", - "keyval-config-err": "Please add configuration JSON" - }, "grid": { "delete-item-title": "Are you sure you want to delete this item?", "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", @@ -1844,23 +1791,25 @@ }, "language": { "language": "Language", - "locales": { - "de_DE": "German", - "fr_FR": "French", - "zh_CN": "Simplified Chinese", - "zh_TW": "Traditional Chinese", + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", "en_US": "English", - "it_IT": "Italian", - "ko_KR": "Korean", - "ru_RU": "Russian", - "es_ES": "Spanish", - "ja_JA": "Japanese", - "tr_TR": "Turkish", - "fa_IR": "Persian", - "uk_UA": "Ukrainian", - "cs_CZ": "Czech", - "el_GR": "Greek", - "lv_LV": "Latvian" + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + } } } diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 75abc889b3..8dcf6767e5 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -1761,21 +1761,23 @@ "language": { "language": "Lenguaje", "locales": { - "de_DE": "Alemán", - "fr_FR": "Francés", - "zh_CN": "Chino", - "en_US": "Inglés", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", "it_IT": "Italiano", - "ko_KR": "Coreano", - "ru_RU": "Ruso", + "ko_KR": "한글", + "ru_RU": "Русский", "es_ES": "Español", - "ja_JA": "Japonés", - "tr_TR": "Turco", - "fa_IR": "Persa", - "uk_UA": "Ucraniano", - "cs_CZ": "Checo", - "el_GR": "Griego", - "lv_LV": "Letón" + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-fa_IR.json b/ui/src/app/locale/locale.constant-fa_IR.json index 34eee792b9..960796b063 100644 --- a/ui/src/app/locale/locale.constant-fa_IR.json +++ b/ui/src/app/locale/locale.constant-fa_IR.json @@ -1626,19 +1626,23 @@ "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": "در چک " + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index f234afbd88..8509b1b054 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -1169,23 +1169,25 @@ "value": "Valeur" }, "language": { - "language": "Language", + "language": "Langue", "locales": { - "de_DE": "Allemand", - "en_US": "Anglais", + "de_DE": "Deutsch", "fr_FR": "Français", - "es_ES": "Espagnol", - "it_IT": "Italien", - "ko_KR": "Coréen", - "ru_RU": "Russe", - "zh_CN": "Chinois", - "ja_JA": "Japonaise", - "tr_TR": "Turc", - "fa_IR": "Persane", - "uk_UA": "Ukrainien", - "cs_CZ": "Tchèque", - "el_GR": "Grec", - "lv_LV": "Letton" + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } }, "layout": { diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 1c65d04096..ecb721d782 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -1702,21 +1702,23 @@ "language": { "language": "Lingua", "locales": { - "de_DE": "Tedesco", - "fr_FR": "Francese", - "zh_CN": "Cinese", - "en_US": "Inglese", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", "it_IT": "Italiano", - "ko_KR": "Coreano", - "ru_RU": "Russo", - "es_ES": "Spagnolo", - "ja_JA": "Giapponese", - "tr_TR": "Turco", - "fa_IR": "Persiana", - "uk_UA": "Ucraino", - "cs_CZ": "Ceco", - "el_GR": "Greco", - "lv_LV": "lettone" + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json index e1ebd4d6b3..a2560e78b1 100644 --- a/ui/src/app/locale/locale.constant-ja_JA.json +++ b/ui/src/app/locale/locale.constant-ja_JA.json @@ -1509,20 +1509,24 @@ }, "language": { "language": "言語", - "locales": { - "de_DE": "ドイツ語", - "fr_FR": "フランス語", - "en_US": "英語", - "ko_KR": "韓国語", - "it_IT": "イタリアの", - "zh_CN": "中国語", - "ru_RU": "ロシア", - "es_ES": "スペイン語", - "ja_JA": "日本語", - "tr_TR": "トルコ語", - "fa_IR": "ペルシャ語", - "uk_UA": "ウクライナ語", - "cs_CZ": "チェコ語で" - } + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json index fde60c77bb..9e6a2043c0 100644 --- a/ui/src/app/locale/locale.constant-ko_KR.json +++ b/ui/src/app/locale/locale.constant-ko_KR.json @@ -1385,19 +1385,23 @@ "language": { "language": "언어", "locales": { - "de_DE": "독일어", - "en_US": "영어", - "fr_FR": "프랑스의", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", "ko_KR": "한글", - "zh_CN": "중국어", - "ru_RU": "러시아어", - "es_ES": "스페인어", - "it_IT": "이탈리아 사람", - "ja_JA": "일본어", - "tr_TR": "터키어", - "fa_IR": "페르시아 인", - "uk_UA": "우크라이나의", - "cs_CZ": "체코 어로" + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-lv_LV.json b/ui/src/app/locale/locale.constant-lv_LV.json index 4ddbb1856f..7221d6c62e 100644 --- a/ui/src/app/locale/locale.constant-lv_LV.json +++ b/ui/src/app/locale/locale.constant-lv_LV.json @@ -1679,19 +1679,23 @@ "language": { "language": "Language", "locales": { - "de_DE": "German", - "fr_FR": "French", - "zh_CN": "Simplified Chinese", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", "en_US": "English", - "it_IT": "Italian", - "ko_KR": "Korean", - "ru_RU": "Russian", - "es_ES": "Spanish", - "ja_JA": "Japanese", - "tr_TR": "Turkish", - "fa_IR": "Persian", - "uk_UA": "Ukrainian", - "cs_CZ": "Czech" + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } -} \ No newline at end of file +} diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 87075014ba..ea2b073f85 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1788,21 +1788,23 @@ "language": { "language": "Язык", "locales": { - "de_DE": "Немецкий", - "en_US": "Английский", - "zh_CN": "Китайский", - "ko_KR": "Корейский", - "es_ES": "Испанский", - "it_IT": "Итальянский", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", "ru_RU": "Русский", - "tr_TR": "Турецкий", - "fr_FR": "Французский", - "ja_JA": "Японский", - "fa_IR": "Персидский", - "uk_UA": "Украинский", - "cs_CZ": "Чешский", - "el_GR": "Греческий", - "lv_LV": "Латышский" + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index c9c5964c52..111a8d3ef3 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -1592,21 +1592,23 @@ "language": { "language": "Dil", "locales": { - "de_DE": "Almanca", - "fr_FR": "Fransızca", - "zh_CN": "Çince", - "en_US": "İngilizce", - "it_IT": "İtalyan", - "ko_KR": "Koreli", - "ru_RU": "Rusça", - "es_ES": "İspanyol", - "ja_JA": "Japonca", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", "tr_TR": "Türkçe", - "fa_IR": "Farsça", - "uk_UA": "Ukrayna", - "cs_CZ": "Çekçe", - "el_GR": "Yunanca", - "lv_LV": "Letonca" + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } -} \ 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 6aa1e06e89..e22cfe6097 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -2394,21 +2394,23 @@ "language": { "language": "Мова", "locales": { - "fr_FR": "Французька", - "zh_CN": "Китайська", - "en_US": "Англійська", - "it_IT": "Італійська", - "ko_KR": "Корейська", - "ru_RU": "Російська", - "es_ES": "Іспанська", - "ja_JA": "Японська", - "tr_TR": "Турецька", - "de_DE": "Німецька", + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", "uk_UA": "Українська", - "fa_IR": "Перська", - "cs_CZ": "Чеська", - "el_GR": "Грецька", - "lv_LV": "Латиська" + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index 0c1f5f743b..bb071f0220 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -1603,20 +1603,23 @@ "language": { "language": "语言", "locales": { - "de_DE": "德文", - "en_US": "英文", - "fr_FR": "法文", - "ko_KR": "韩文", + "de_DE": "Deutsch", + "fr_FR": "Français", "zh_CN": "简体中文", - "zh_TW": "繁体中文", - "ru_RU": "俄文", - "es_ES": "西班牙文", - "it_IT": "意大利文", - "ja_JA": "日文", - "tr_TR": "土耳其文", - "fa_IR": "波斯文", - "uk_UA": "乌克兰文", - "cs_CZ": "捷克文" + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" } } } diff --git a/ui/src/app/locale/locale.constant-zh_TW.json b/ui/src/app/locale/locale.constant-zh_TW.json index acaa9ac53c..aa38940a4e 100644 --- a/ui/src/app/locale/locale.constant-zh_TW.json +++ b/ui/src/app/locale/locale.constant-zh_TW.json @@ -1602,21 +1602,24 @@     },     "language": {         "language": "語言", -        "locales": { -            "de_DE": "德文", -            "en_US": "英文", -            "fr_FR": "法文", -            "ko_KR": "韓文", -            "zh_CN": "簡體中文", -            "zh_TW": "繁體中文", -            "ru_RU": "俄文", -            "es_ES": "西班牙文", -            "it_IT": "意大利文", -            "ja_JA": "日文", -            "tr_TR": "土耳其文", -            "fa_IR": "波斯文", -            "uk_UA": "烏克蘭文", -            "cs_CZ": "捷克文" -        } + "locales": { + "de_DE": "Deutsch", + "fr_FR": "Français", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "English", + "it_IT": "Italiano", + "ko_KR": "한글", + "ru_RU": "Русский", + "es_ES": "Español", + "ja_JA": "日本語", + "tr_TR": "Türkçe", + "fa_IR": "فارسي", + "uk_UA": "Українська", + "cs_CZ": "Česky", + "el_GR": "Ελληνικά", + "ro_RO": "Română", + "lv_LV": "Latviešu" + }     } } From b3abfd38666a0b2cc6900a7791d34ee692514786 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 6 Feb 2020 16:31:54 +0200 Subject: [PATCH 186/261] Refactoring gateway configuration form (#2389) * Change translate, layout and clear code * Refactoring code * Refactoring code * Refactoring code * Code refactoring --- .../widget_bundles/gateway_widgets.json | 20 +- .../gateWay/gateway-config.directive.js | 317 ----------- .../gateWay/gateway-config.tpl.html | 94 ---- .../gateWay/gateway-form.directive.js | 498 ------------------ .../components/gateWay/gateway-form.tpl.html | 219 -------- .../gateway-config-dialog.tpl.html | 6 +- .../gateway-config-select.directive.js | 52 +- .../gateway-config-select.scss | 0 .../gateway-config-select.tpl.html | 19 +- .../gateway/gateway-config.directive.js | 170 ++++++ .../{gateWay => gateway}/gateway-config.scss | 22 +- .../gateway/gateway-config.tpl.html | 81 +++ .../gateway/gateway-form.directive.js | 467 ++++++++++++++++ .../{gateWay => gateway}/gateway-form.scss | 10 +- .../components/gateway/gateway-form.tpl.html | 227 ++++++++ .../import-export/import-export.service.js | 16 +- ui/src/app/layout/index.js | 6 +- ui/src/app/locale/locale.constant-en_US.json | 102 ++-- 18 files changed, 1078 insertions(+), 1248 deletions(-) delete mode 100644 ui/src/app/components/gateWay/gateway-config.directive.js delete mode 100644 ui/src/app/components/gateWay/gateway-config.tpl.html delete mode 100644 ui/src/app/components/gateWay/gateway-form.directive.js delete mode 100644 ui/src/app/components/gateWay/gateway-form.tpl.html rename ui/src/app/components/{gateWay => gateway}/gateway-config-dialog.tpl.html (92%) rename ui/src/app/components/{gateWay => gateway}/gateway-config-select.directive.js (74%) rename ui/src/app/components/{gateWay => gateway}/gateway-config-select.scss (100%) rename ui/src/app/components/{gateWay => gateway}/gateway-config-select.tpl.html (76%) create mode 100644 ui/src/app/components/gateway/gateway-config.directive.js rename ui/src/app/components/{gateWay => gateway}/gateway-config.scss (85%) create mode 100644 ui/src/app/components/gateway/gateway-config.tpl.html create mode 100644 ui/src/app/components/gateway/gateway-form.directive.js rename ui/src/app/components/{gateWay => gateway}/gateway-form.scss (90%) create mode 100644 ui/src/app/components/gateway/gateway-form.tpl.html diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json index ce78185c29..669f706d15 100644 --- a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -22,23 +22,19 @@ } }, { - "alias": "new_config_form", - "name": "Config form", + "alias": "gateway_configuration", + "name": "Gateway Configuration", "descriptor": { "type": "static", - "sizeX": 7.5, - "sizeY": 10.5, - "resources": [ - { - "url": "" - } - ], + "sizeX": 8, + "sizeY": 6.5, + "resources": [], "templateHtml": "\n\n", - "templateCss": "#container {\n overflow: auto;\n height: 100%;\n margin: auto;\n}\n\n\n\n/*#configurations {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n/* height: 100%;*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/*}*/\n\n/*.configurationPointParent {*/\n/* display: flex;*/\n/* flex-direction: column;*/\n \n/*}*/\n\n/*.configurationPoint {*/\n/* display: flex;*/\n/* flex-direction: row;*/\n/* justify-content: space-between;*/\n/* margin: 5px;*/\n/*}*/\n\n/*.configurationPoint.select {*/\n/* margin: 0px;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n\n/*}*/\n\n/*.configurationPoint.select.inputRow {*/\n/* margin: 0px;*/\n/* width: 100%;*/\n/* padding: 0;*/\n/* border: 0;*/\n/* height: 40px;*/\n/*}*/\n\n\n/*.error {*/\n/*color: red;*/\n/*}*/", + "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.formId = \"form-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n self.ctx.$scope.$broadcast('gateway-form-resize', self.ctx.$scope.formId);\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"GatewayConfigForm\",\n \"properties\": {\n \"gatewayTitle\": {\n \"title\": \"Gateway form title\",\n \"type\": \"string\",\n \"default\": \"Gateway Config Form\"\n }\n }\n },\n \"form\": [\n \"gatewayTitle\"\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\": \"Gatwey Configuration\"\n },\n \"archiveFileName\": {\n \"title\": \"Default archive file name\",\n \"type\": \"string\",\n \"default\": \"gatewayConfiguration\"\n },\n \"gatewayType\": {\n \"title\": \"Device type for new gateway\",\n \"type\": \"string\",\n \"default\": \"Gateway\"\n },\n \"successfulSave\": {\n \"title\": \"Text message about successfully saved gateway configuration\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"gatewayNameExists\": {\n \"title\": \"Text message when device with entered name is already exists\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n [\n \"widgetTitle\",\n \"archiveFileName\",\n \"gatewayType\"\n ],\n [\n \"successfulSave\",\n \"gatewayNameExists\"\n ]\n ],\n \"groupInfoes\": [{\n \"formIndex\": 0,\n \"GroupTitle\": \"General settings\"\n }, {\n \"formIndex\": 1,\n \"GroupTitle\": \"Messages 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\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"gatewayTitle\":\"Gateway Config Form\"},\"title\":\"Config form\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":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\":{\"widgetTitle\":\"Gatwey Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } } ] diff --git a/ui/src/app/components/gateWay/gateway-config.directive.js b/ui/src/app/components/gateWay/gateway-config.directive.js deleted file mode 100644 index 66ba36620c..0000000000 --- a/ui/src/app/components/gateWay/gateway-config.directive.js +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright © 2016-2020 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. - */ -import './gateway-config.scss'; - -/* eslint-disable import/no-unresolved, import/default */ - -import gatewayTemplate from './gateway-config.tpl.html'; -import gatewayDialogTemplate from './gateway-config-dialog.tpl.html'; -import beautify from "js-beautify"; - -/* eslint-enable import/no-unresolved, import/default */ -const js_beautify = beautify.js; - -export default angular.module('thingsboard.directives.gatewayConfig', []) - .directive('tbGatewayConfig', GatewayConfig) - .name; - -/*@ngInject*/ -function GatewayConfig() { - return { - restrict: "E", - scope: true, - bindToController: { - disabled: '=ngDisabled', - titleText: '@?', - keyPlaceholderText: '@?', - valuePlaceholderText: '@?', - noDataText: '@?', - gatewayConfig: '=', - changeAlignment: '=' - }, - controller: GatewayConfigController, - controllerAs: 'vm', - templateUrl: gatewayTemplate - }; -} - -/*@ngInject*/ -function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types, toast, $timeout, $compile, $translate) { //eslint-disable-line - - let vm = this; - - vm.kvList = []; - vm.types = types; - $scope.$watch('vm.gatewayConfig', () => { - vm.stopWatchKvList(); - vm.kvList.length = 0; - if (vm.gatewayConfig) { - for (var property in vm.gatewayConfig) { - if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) { - vm.kvList.push( - { - enabled: vm.gatewayConfig[property].enabled, - key: property + '', - value: vm.gatewayConfig[property].connector + '', - config: js_beautify(vm.gatewayConfig[property].config + '', {indent_size: 4}) - } - ); - } - } - } - $mdUtil.nextTick(() => { - vm.watchKvList(); - }); - }); - - vm.watchKvList = () => { - $scope.kvListWatcher = $scope.$watch('vm.kvList', () => { - if (!vm.gatewayConfig) { - return; - } - for (let property in vm.gatewayConfig) { - if (Object.prototype.hasOwnProperty.call(vm.gatewayConfig, property)) { - delete vm.gatewayConfig[property]; - } - } - for (let i = 0; i < vm.kvList.length; i++) { - let entry = vm.kvList[i]; - if (entry.key && entry.value) { - let connectorJSON = angular.toJson({ - enabled: entry.enabled, - connector: entry.value, - config: angular.fromJson(entry.config) - }); - vm.gatewayConfig [entry.key] = angular.fromJson(connectorJSON); - } - } - }, true); - }; - - vm.stopWatchKvList = () => { - if ($scope.kvListWatcher) { - $scope.kvListWatcher(); - $scope.kvListWatcher = null; - } - }; - - vm.removeKeyVal = (index) => { - if (index > -1) { - vm.kvList.splice(index, 1); - } - }; - - vm.addKeyVal = () => { - if (!vm.kvList) { - vm.kvList = []; - } - vm.kvList.push( - { - enabled: false, - key: '', - value: '', - config: '{}' - } - ); - } - - vm.openConfigDialog = ($event, index, config, typeName) => { - if ($event) { - $event.stopPropagation(); - } - $mdDialog.show({ - controller: GatewayDialogController, - controllerAs: 'vm', - templateUrl: gatewayDialogTemplate, - parent: angular.element($document[0].body), - locals: { - config: config, - typeName: typeName - }, - targetEvent: $event, - fullscreen: true, - multiple: true, - }).then(function (config) { - if (config) { - if (index > -1) { - vm.kvList[index].config = config; - } - } - }, function () { - }); - - }; - - vm.configTypeChange = (keyVal) => { - for (let prop in types.gatewayConfigType) { - if (types.gatewayConfigType[prop].value === keyVal.value) { - if (!keyVal.key) { - keyVal.key = vm.configTypeChangeValid(types.gatewayConfigType[prop].name, 0); - } - } - } - vm.checkboxValid(keyVal); - }; - - vm.keyValChange = (keyVal, indexKey) => { - keyVal.key = vm.keyValChangeValid(keyVal.key, 0, indexKey); - vm.checkboxValid(keyVal); - }; - - vm.configTypeChangeValid = (name, index) => { - let newKeyName = index ? name + index : name; - let indexRes = vm.kvList.findIndex((element) => element.key === newKeyName); - return indexRes === -1 ? newKeyName : vm.configTypeChangeValid(name, ++index); - }; - - vm.keyValChangeValid = (name, index, indexKey) => { - angular.forEach(vm.kvList, function (value, key) { - let nameEq = (index === 0) ? name : name + index; - if (key !== indexKey && value.key && value.key === nameEq) { - index++; - vm.keyValChangeValid(name, index, indexKey); - } - - }); - return (index === 0) ? name : name + index; - }; - - vm.buttonValid = (config) => { - return (angular.equals("{}", config)) ? "md-warn" : "md-primary"; - }; - - vm.checkboxValid = (keyVal) => { - if (!keyVal.key || angular.equals("", keyVal.key) - || !keyVal.value || angular.equals("", keyVal.value) - || angular.equals("{}", keyVal.config)) { - return keyVal.enabled = false; - } - return true; - }; - vm.checkboxValidMouseover = ($event, keyVal) => { - console.log($event, keyVal); //eslint-disable-line - vm.checkboxValidClick ($event, keyVal); - }; - - vm.checkboxValidClick = ($event, keyVal) => { - if (!vm.checkboxValid(keyVal)) { - let errTxt = ""; - if (!keyVal.key || angular.equals("", keyVal.key)) { - errTxt = $translate.instant('gateway.keyval-name-err'); - } - - if (!keyVal.value || angular.equals("", keyVal.value)) { - errTxt += '
' + $translate.instant('gateway.keyval-type-err') + '
'; - } - - if (angular.equals("{}", keyVal.config)) { - errTxt += '
' + $translate.instant('gateway.keyval-config-err') + '
'; - } - if (!angular.equals("", errTxt)) { - displayTooltip($event, '
' + - '
' + - '
' + $translate.instant('gateway.keyval-save-err') + '
' + - '
' + errTxt + '
' + - '
' + - '
'); - } - } - else { - destroyTooltips(); - } - }; - - - function displayTooltip(event, content) { - destroyTooltips(); - vm.tooltipTimeout = $timeout(() => { - var element = angular.element(event.target); - element.tooltipster( - { - theme: 'tooltipster-shadow', - delay: 10, - animation: 'grow', - side: 'right' - } - ); - var contentElement = angular.element(content); - $compile(contentElement)($scope); - var tooltip = element.tooltipster('instance'); - tooltip.content(contentElement); - tooltip.open(); - }, 500); - } - - function destroyTooltips() { - if (vm.tooltipTimeout) { - $timeout.cancel(vm.tooltipTimeout); - vm.tooltipTimeout = null; - } - var instances = angular.element.tooltipster.instances(); - instances.forEach((instance) => { - if (!instance.isErrorTooltip) { - instance.destroy(); - } - }); - } -} - -/*@ngInject*/ -function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) { - let vm = this; - vm.doc = $document[0]; - vm.config = angular.copy(config); - vm.typeName = "" + typeName; - vm.configAreaOptions = { - useWrapMode: false, - mode: 'json', - showGutter: true, - showPrintMargin: true, - theme: 'github', - advanced: { - enableSnippets: true, - enableBasicAutocompletion: true, - enableLiveAutocompletion: true - }, - onLoad: function (_ace) { - _ace.$blockScrolling = 1; - } - }; - - vm.validateConfig = (model, editorName) => { - if (model && model.length) { - try { - angular.fromJson(model); - $scope.theForm[editorName].$setValidity('configJSON', true); - } catch (e) { - $scope.theForm[editorName].$setValidity('configJSON', false); - } - } - }; - - vm.save = () => { - $mdDialog.hide(vm.config); - }; - - vm.cancel = () => { - $mdDialog.hide(); - }; - - vm.beautifyJson = () => { - vm.config = js_beautify(vm.config, {indent_size: 4}); - }; -} - diff --git a/ui/src/app/components/gateWay/gateway-config.tpl.html b/ui/src/app/components/gateWay/gateway-config.tpl.html deleted file mode 100644 index 565c771099..0000000000 --- a/ui/src/app/components/gateWay/gateway-config.tpl.html +++ /dev/null @@ -1,94 +0,0 @@ - -
-
-
- - - - - {{ 'gateway.enabled' | translate }} - - -
-
- - - - - {{configType.value}} - - - - {{ 'gateway.connector-type' | translate }} - - - - -
-
extension.field-required
-
- - {{ 'gateway.name' | translate }} - -
-
-
- - settings_ethernet - - {{ 'gateway.update-config' | translate }} - - - - close - - {{ 'gateway.delete' | translate }} - - -
-
- {{vm.noDataText ? vm.noDataText : 'gateway.no-connectors'}} -
- - - {{ 'gateway.add-connectors' | translate }} - - action.add - -
-
diff --git a/ui/src/app/components/gateWay/gateway-form.directive.js b/ui/src/app/components/gateWay/gateway-form.directive.js deleted file mode 100644 index 2aa05ce004..0000000000 --- a/ui/src/app/components/gateWay/gateway-form.directive.js +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright © 2016-2020 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. - */ -import './gateway-form.scss'; -/* eslint-disable import/no-unresolved, import/default */ - -import gatewayFormTemplate from './gateway-form.tpl.html'; - -/* eslint-enable import/no-unresolved, import/default */ - -export default angular.module('thingsboard.directives.gatewayForm', []) - .directive('tbGatewayForm', GatewayForm) - .name; - -/*@ngInject*/ -function GatewayForm() { - return { - restrict: "E", - scope: true, - bindToController: { - disabled: '=ngDisabled', - keyPlaceholderText: '@?', - valuePlaceholderText: '@?', - noDataText: '@?', - formId: '=', - ctx: '=', - gatewayFormConfig: '=', - theForm: '=' - }, - controller: GatewayFormController, - controllerAs: 'vm', - templateUrl: gatewayFormTemplate - }; -} - -/*@ngInject*/ -function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q) { - $scope.$mdExpansionPanel = $mdExpansionPanel; - let vm = this; - const attributeNameClinet = "current_configuration"; - const attributeNameServer = "configuration_drafts"; - const attributeNameShared = "configuration"; - const attributeNameLogShared = "RemoteLoggingLevel"; - vm.remoteLoggingConfig = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"'; - vm.types = types; - - vm.configurations = { - singleSelect: '', - host: $document[0].domain, - port: 1883, - remoteConfiguration: true, - accessToken: '', - entityType: '', - entityId: '', - storageType: "memoryStorage", // "memoryStorage"; fileStorage - readRecordsCount: 100, - maxRecordsCount: 10000, - dataFolderPath: './data/', - maxFilesCount: 5, - securityType: "accessToken", // "accessToken", "tls" - caCertPath: '/etc/thingsboard-gateway/ca.pem', - privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem', - certPath: '/etc/thingsboard-gateway/certificate.pem', - connectors: {}, - remoteLoggingLevel: "DEBUG", // level login - remoteLoggingPathToLogs: './logs/' - }; - getGatewaysListByUser(true); - - vm.securityTypes = [{ - name: 'Access Token', - value: 'accessToken' - }, { - name: 'TLS', - value: 'tls' - }]; - - vm.storageTypes = [{ - name: 'Memory storage', - value: 'memoryStorage' - }, { - name: 'File storage', - value: 'fileStorage' - }]; - - $scope.$on('gateway-form-resize', function (event, formId) { - if (vm.formId == formId) { - updateWidgetDisplaying(); - } - }); - - function updateWidgetDisplaying() { - if (vm.ctx && vm.ctx.$container) { - vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425); - } - } - - updateWidgetDisplaying(); - - vm.getAccessToken = (deviceObj) => { - if (deviceObj.name) { - deviceService.findByName(deviceObj.name, {ignoreErrors: true}) - .then( - function (device) { - getDeviceCredential(device.id.id); - } - ) - } - }; - - function getDeviceCredential(deviceId) { - return deviceService.getDeviceCredentials(deviceId).then( - (deviceCredentials) => { - vm.configurations.accessToken = deviceCredentials.credentialsId; - vm.configurations.entityType = deviceCredentials.deviceId.entityType; - vm.configurations.entityId = deviceCredentials.deviceId.id; - vm.getAttributeStart(); - } - ); - } - - vm.createDevice = (deviceObj) => { - deviceService.findByName(deviceObj.name, {ignoreErrors: true}) - .then( - function (device) { - getDeviceCredential(device.id.id).then(() => { - getGatewaysListByUser(); - }); - }, - function () { - deviceService.saveDevice(deviceObj).then( - (device) => { - deviceService.getDeviceCredentials(device.id.id).then( - (data) => { - vm.configurations.accessToken = data.credentialsId; - vm.configurations.entityType = device.id.entityType; - vm.configurations.entityId = device.id.id; - vm.getAttributeStart(); - getGatewaysListByUser(); - } - ); - } - ); - }); - }; - - vm.saveAttributeConfig = () => { - vm.setAttribute(attributeNameShared, $window.btoa(angular.toJson(vm.getConfigAllByAttributeJSON())), types.attributesScope.shared.value); - vm.setAttribute(attributeNameServer, $window.btoa(angular.toJson(vm.getConfigByAttributeTmpJSON())), types.attributesScope.server.value); - vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); - }; - - vm.getAttributeStart = () => { - let initResps = []; - vm.configurations.connectors = {}; - initResps.push(vm.getAttributeConfig(attributeNameClinet, types.attributesScope.client.value)); - initResps.push(vm.getAttributeConfig(attributeNameServer, types.attributesScope.server.value)); - initResps.push(vm.getAttributeConfig(attributeNameLogShared, types.attributesScope.shared.value)); - $q.all(initResps).then((resp) => { - vm.getAttributeInitFromClient(resp[0]); - vm.getAttributeInitFromServer(resp[1]); - vm.getAttributeInitFromShared(resp[2]); - }, (err) => { - console.log("getAttribute_error", err); //eslint-disable-line - }); - }; - - vm.getAttributeConfig = (attributeName, typeValue) => { - let keys = [attributeName]; - return attributeService.getEntityAttributesValues(vm.configurations.entityType, vm.configurations.entityId, typeValue, keys); - }; - - vm.setAttribute = (attributeName, attributeConfig, typeValue) => { - let attributes = [ - { - key: attributeName, - value: attributeConfig - } - ]; - attributeService.saveEntityAttributes(vm.configurations.entityType, vm.configurations.entityId, typeValue, attributes).then(() => { - }, (err) => { - console.log("setAttribute_", err); //eslint-disable-line - }); - }; - - vm.exportConfig = () => { - let fileZip = {}; - fileZip["tb_gateway.yaml"] = vm.getConfig(); - vm.createConfigByExport(fileZip); - vm.getLogsConfigByExport(fileZip); - importExport.exportJSZip(fileZip, 'config'); - vm.setAttribute(attributeNameLogShared, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); - }; - - vm.getConfig = () => { - let config; - config = 'thingsboard:\n'; - config += ' host: ' + vm.configurations.host + '\n'; - config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n'; - config += ' port: ' + vm.configurations.port + '\n'; - config += ' security:\n'; - if (vm.configurations.securityType === 'accessToken') { - config += ' access-token: ' + vm.configurations.accessToken + '\n'; - } else if (vm.configurations.securityType === 'tls') { - config += ' ca_cert: ' + vm.configurations.caCertPath + '\n'; - config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n'; - config += ' cert: ' + vm.configurations.certPath + '\n'; - } - config += 'storage:\n'; - if (vm.configurations.storageType === 'memoryStorage') { - config += ' type: memory\n'; - config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n'; - config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n'; - } else if (vm.configurations.storageType === 'fileStorage') { - config += ' type: file\n'; - config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n'; - config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n'; - config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n'; - config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n'; - } - config += 'connectors:\n'; - for (let connector in vm.configurations.connectors) { - if (vm.configurations.connectors[connector].enabled) { - config += ' -\n'; - config += ' name: ' + connector + ' Connector\n'; - config += ' type: ' + vm.configurations.connectors[connector].connector + '\n'; - config += ' configuration: ' + vm.validFileName(connector) + ".json" + '\n'; - } - } - return config; - }; - - vm.createConfigByExport = (fileZipAdd) => { - for (let connector in vm.configurations.connectors) { - if (vm.configurations.connectors[connector].enabled) { - fileZipAdd[vm.validFileName(connector) + ".json"] = angular.toJson(vm.configurations.connectors[connector].config); - } - } - }; - - vm.getLogsConfigByExport = (fileZipAdd) => { - fileZipAdd["logs.conf"] = vm.getLogsConfig(); - }; - - vm.getLogsConfig = () => { - return vm.remoteLoggingConfig - .replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel) - .replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs); - }; - - vm.getConfigAllByAttributeJSON = () => { - let thingsBoardAll = {}; - thingsBoardAll["thingsboard"] = vm.getConfigMainByAttributeJSON(); - vm.getConfigByAttributeJSON(thingsBoardAll); - return thingsBoardAll; - }; - - vm.getConfigMainByAttributeJSON = () => { - let configMain = {}; - let thingsBoard = {}; - thingsBoard.host = vm.configurations.host; - thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration; - thingsBoard.port = vm.configurations.port; - let security = {}; - if (vm.configurations.securityType === 'accessToken') { - security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : "" - } else { - security.caCert = vm.configurations.caCertPath; - security.privateKey = vm.configurations.privateKeyPath; - security.cert = vm.configurations.certPath; - } - thingsBoard.security = security; - configMain.thingsboard = thingsBoard; - - let storage = {}; - if (vm.configurations.storageType === 'memoryStorage') { - storage.type = "memory"; - storage.read_records_count = vm.configurations.readRecordsCount; - storage.max_records_count = vm.configurations.maxRecordsCount; - } else if (vm.configurations.storageType === 'fileStorage') { - storage.type = "file"; - storage.data_folder_path = vm.configurations.dataFolderPath; - storage.max_file_count = vm.configurations.maxFilesCount; - storage.max_read_records_count = vm.configurations.readRecordsCount; - storage.max_records_per_file = vm.configurations.maxRecordsCount; - } - configMain.storage = storage; - - let conn = []; - for (let connector in vm.configurations.connectors) { - if (vm.configurations.connectors[connector].enabled) { - let connect = {}; - connect.configuration = vm.validFileName(connector) + ".json"; - connect.name = connector; - connect.type = vm.configurations.connectors[connector].connector; - conn.push(connect); - } - } - configMain.connectors = conn; - - configMain.logs = $window.btoa(vm.getLogsConfig()); - - return configMain; - }; - - vm.getConfigByAttributeJSON = (thingsBoardBy) => { - for (let connector in vm.configurations.connectors) { - if (vm.configurations.connectors[connector].enabled) { - let typeAr = vm.configurations.connectors[connector].connector; - let objTypeAll = []; - for (let conn in vm.configurations.connectors) { - if (typeAr === vm.configurations.connectors[conn].connector && vm.configurations.connectors[conn].enabled) { - let objType = {}; - objType["name"] = conn; - objType["config"] = vm.configurations.connectors[conn].config; - objTypeAll.push(objType); - } - } - if (objTypeAll.length > 0) { - thingsBoardBy[typeAr] = objTypeAll; - } - } - } - }; - - vm.getConfigByAttributeTmpJSON = () => { - let connects = {}; - for (let connector in vm.configurations.connectors) { - if (!vm.configurations.connectors[connector].enabled && Object.keys(vm.configurations.connectors[connector].config).length !== 0) { - let conn = {}; - conn["connector"] = vm.configurations.connectors[connector].connector; - conn["config"] = vm.configurations.connectors[connector].config; - connects[connector] = conn; - } - } - return connects; - }; - - function getGatewaysListByUser(firstInit) { - vm.gateways = []; - vm.currentUser = userService.getCurrentUser(); - if (vm.currentUser.authority === 'TENANT_ADMIN') { - deviceService.getTenantDevices({limit: 500}).then( - (devices) => { - if (devices.data.length > 0) { - devices.data.forEach((device) => { - if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { - vm.gateways.push(device.name); - if (firstInit && vm.gateways.length && device.name === vm.gateways[0]) { - vm.configurations.singleSelect = vm.gateways[0]; - let deviceObj = { - "name": vm.configurations.singleSelect, - "type": "Gateway", - "additionalInfo": { - "gateway": true - } - }; - vm.getAccessToken(deviceObj); - } - } - }); - } - } - ); - } else if (vm.currentUser.authority === 'CUSTOMER_USER') { - deviceService.getCustomerDevices(vm.currentUser.customerId, {limit: 500}).then( - (devices) => { - if (devices.data.length > 0) { - devices.data.forEach((device) => { - if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { - vm.gateways.push(device.name); - if (firstInit && vm.gateways.length) { - vm.configurations.singleSelect = vm.gateways[0]; - let deviceObj = { - "name": vm.configurations.singleSelect, - "type": "Gateway", - "additionalInfo": { - "gateway": true - } - }; - vm.getAccessToken(deviceObj); - } - } - }); - } - } - ); - } - } - - vm.getAttributeInitFromClient = (resp) => { - if (resp.length > 0) { - vm.configurations.connectors = {}; - let attribute = angular.fromJson($window.atob(resp[0].value)); - for (var type in attribute) { - let keyVal = attribute[type]; - if (type === "thingsboard") { - if (keyVal !== null && Object.keys(keyVal).length > 0) { - vm.setConfigMain(keyVal); - } - } else { - for (let typeVal in keyVal) { - let typeName = ''; - if (Object.prototype.hasOwnProperty.call(keyVal[typeVal], 'name')) { - typeName = 'name'; - } - let key = ""; - key = (typeName === "") ? "No name" : ((typeName === 'name') ? keyVal[typeVal].name : keyVal[typeVal][typeName].name); - let conn = {}; - conn["enabled"] = true; - conn["connector"] = type; - conn["config"] = angular.toJson(keyVal[typeVal].config); - vm.configurations.connectors[key] = conn; - } - } - } - } - }; - - vm.getAttributeInitFromServer = (resp) => { - if (resp.length > 0) { - let attribute = angular.fromJson($window.atob(resp[0].value)); - for (let key in attribute) { - let conn = {}; - conn["enabled"] = false; - conn["connector"] = attribute[key].connector; - conn["config"] = angular.toJson(attribute[key].config); - vm.configurations.connectors[key] = conn; - } - } - }; - - vm.getAttributeInitFromShared = (resp) => { - if (resp.length > 0) { - if (vm.types.gatewayLogLevel[resp[0].value.toLowerCase()]) { - vm.configurations.remoteLoggingLevel = resp[0].value.toUpperCase(); - } - } else { - vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug; - } - }; - - vm.setConfigMain = (keyVal) => { - if (Object.prototype.hasOwnProperty.call(keyVal, 'thingsboard')) { - vm.configurations.host = keyVal.thingsboard.host; - vm.configurations.port = keyVal.thingsboard.port; - vm.configurations.remoteConfiguration = keyVal.thingsboard.remoteConfiguration; - if (Object.prototype.hasOwnProperty.call(keyVal.thingsboard.security, 'accessToken')) { - vm.configurations.securityType = 'accessToken'; - vm.configurations.accessToken = keyVal.thingsboard.security.accessToken; - } else { - vm.configurations.securityType = 'tls'; - vm.configurations.caCertPath = keyVal.thingsboard.security.caCert; - vm.configurations.privateKeyPath = keyVal.thingsboard.security.private_key; - vm.configurations.certPath = keyVal.thingsboard.security.cert; - } - } - if (Object.prototype.hasOwnProperty.call(keyVal, 'storage') && Object.prototype.hasOwnProperty.call(keyVal.storage, 'type')) { - if (keyVal.storage.type === 'memory') { - vm.configurations.storageType = 'memoryStorage'; - vm.configurations.readRecordsCount = keyVal.storage.read_records_count; - vm.configurations.maxRecordsCount = keyVal.storage.max_records_count; - } else if (keyVal.storage.type === 'file') { - vm.configurations.storageType = 'fileStorage'; - vm.configurations.dataFolderPath = keyVal.storage.data_folder_path; - vm.configurations.maxFilesCount = keyVal.storage.max_file_count; - vm.configurations.readRecordsCount = keyVal.storage.read_records_count; - vm.configurations.maxRecordsCount = keyVal.storage.max_records_count; - } - } - }; - - vm.setSaveTypeConfig = (itemVal) => { - vm.configurations.remoteConfiguration = itemVal.item; - }; - - vm.validFileName = (fileName) => { - let fileName1 = fileName.replace("_", ""); - let fileName2 = fileName1.replace("-", ""); - let fileName3 = fileName2.replace(/^\s+|\s+$/g, ''); - let fileName4 = fileName3.toLowerCase(); - return fileName4; - }; -} - - diff --git a/ui/src/app/components/gateWay/gateway-form.tpl.html b/ui/src/app/components/gateWay/gateway-form.tpl.html deleted file mode 100644 index 8ea0cfcc07..0000000000 --- a/ui/src/app/components/gateWay/gateway-form.tpl.html +++ /dev/null @@ -1,219 +0,0 @@ - -
- - - -
{{ 'gateway.thingsboard' | translate | uppercase }}
- - -
- - -
{{ 'gateway.thingsboard' | translate | uppercase }}
- - -
- - - - - - - - {{securityType.name}} - - - -
- - - -
-
extension.field-required
-
-
- - - -
-
extension.field-required
-
max
-
min
-
-
-
-
- - - - - - - - - - - - -
- - {{ 'gateway.remote' | translate }} - {{'gateway.remote-tip' | translate }} - -
- - - - - {{loggingLevel}} - - - - - - -
-
extension.field-required
-
-
-
-
-
-
- - -
{{ 'gateway.storage' | translate | uppercase }}
- - -
- - -
{{ 'gateway.storage' | translate | uppercase }}
- - -
- - - - - - {{storageType.name}} - - - - -
- - - -
-
extension.field-required
-
-
- - - - -
-
extension.field-required
-
-
-
- -
- - - -
-
extension.field-required
-
-
- - - - -
-
extension.field-required
-
-
-
-
-
-
- - -
{{ 'gateway.connectors' | translate | uppercase }}
- - -
- - -
{{ 'gateway.connectors' | translate | uppercase }}
- - -
- - - - -
-
-
-
- - {{'action.download' | translate }} - {{'gateway.download-tip' | translate }} - - - - {{'action.save' | translate }} - {{'gateway.save-tip' | translate }} - -
-
diff --git a/ui/src/app/components/gateWay/gateway-config-dialog.tpl.html b/ui/src/app/components/gateway/gateway-config-dialog.tpl.html similarity index 92% rename from ui/src/app/components/gateWay/gateway-config-dialog.tpl.html rename to ui/src/app/components/gateway/gateway-config-dialog.tpl.html index ce55d15f48..87195ac584 100644 --- a/ui/src/app/components/gateWay/gateway-config-dialog.tpl.html +++ b/ui/src/app/components/gateway/gateway-config-dialog.tpl.html @@ -55,15 +55,15 @@ required> - - {{'action.save'|translate}} diff --git a/ui/src/app/components/gateWay/gateway-config-select.directive.js b/ui/src/app/components/gateway/gateway-config-select.directive.js similarity index 74% rename from ui/src/app/components/gateWay/gateway-config-select.directive.js rename to ui/src/app/components/gateway/gateway-config-select.directive.js index 5178fed6d6..79ff454475 100644 --- a/ui/src/app/components/gateWay/gateway-config-select.directive.js +++ b/ui/src/app/components/gateway/gateway-config-select.directive.js @@ -17,7 +17,7 @@ import './gateway-config-select.scss'; /* eslint-disable import/no-unresolved, import/default */ -import gatewayAliasSelectTemplate from './gateway-config-select.tpl.html'; +import gatewaySelectTemplate from './gateway-config-select.tpl.html'; /* eslint-enable import/no-unresolved, import/default */ @@ -32,23 +32,26 @@ export default angular.module('thingsboard.directives.gatewayConfigSelect', []) function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) { var linker = function (scope, element, attrs, ngModelCtrl) { - var template = $templateCache.get(gatewayAliasSelectTemplate); + const template = $templateCache.get(gatewaySelectTemplate); element.html(template); scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; - - scope.ngModelCtrl = ngModelCtrl; - scope.singleSelect = null; + scope.gateway = null; + scope.gatewaySearchText = ''; scope.updateValidity = function () { var value = ngModelCtrl.$viewValue; var valid = angular.isDefined(value) && value != null || !scope.tbRequired; - ngModelCtrl.$setValidity('singleSelect', valid); + ngModelCtrl.$setValidity('gateway', valid); }; - scope.$watch('singleSelect', function () { - scope.updateView(); - }); + function startWatchers() { + scope.$watch('gateway', function (newVal, prevVal) { + if (!angular.equals(newVal, prevVal) && newVal !== null) { + scope.updateView(); + } + }); + } scope.gatewayNameSearch = function (gatewaySearchText) { return gatewaySearchText ? scope.gatewayList.filter( @@ -58,22 +61,20 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, scope.createFilterForGatewayName = function (query) { var lowercaseQuery = query.toLowerCase(); return function filterFn(device) { - return (device.toLowerCase().indexOf(lowercaseQuery) === 0); + return (device.name.toLowerCase().indexOf(lowercaseQuery) === 0); }; }; scope.updateView = function () { - ngModelCtrl.$setViewValue(scope.singleSelect); + ngModelCtrl.$setViewValue(scope.gateway); scope.updateValidity(); - let deviceObj = {"name": scope.singleSelect, "type": "Gateway", "additionalInfo": { - "gateway": true - }}; - scope.getAccessToken(deviceObj); + scope.getAccessToken(scope.gateway.id); }; ngModelCtrl.$render = function () { if (ngModelCtrl.$viewValue) { - scope.singleSelect = ngModelCtrl.$viewValue; + scope.gateway = ngModelCtrl.$viewValue; + startWatchers(); } }; @@ -85,9 +86,9 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { $event.preventDefault(); let indexRes = scope.gatewayList.findIndex((element) => element.key === scope.gatewaySearchText); - if (indexRes === -1) { - scope.createNewGatewayDialog($event, {name: scope.gatewaySearchText}); - } + if (indexRes === -1) { + scope.createNewGatewayDialog($event, scope.gatewaySearchText); + } } }; @@ -96,7 +97,7 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $event.stopPropagation(); } var title = $translate.instant('gateway.create-new-gateway'); - var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName.name}); + var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName}); var confirm = $mdDialog.confirm() .targetEvent($event) .title(title) @@ -106,9 +107,13 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, .ok($translate.instant('action.yes')); $mdDialog.show(confirm).then( () => { - let deviceObj = {"name": deviceName.name, "type": "Gateway", "additionalInfo": { - "gateway": true - }}; + let deviceObj = { + name: deviceName, + type: "Gateway", + additionalInfo: { + gateway: true + } + }; scope.createDevice(deviceObj); }, () => { @@ -125,7 +130,6 @@ function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, link: linker, scope: { tbRequired: '=?', - allowedEntityTypes: '=?', gatewayList: '=?', getAccessToken: '=', createDevice: '=', diff --git a/ui/src/app/components/gateWay/gateway-config-select.scss b/ui/src/app/components/gateway/gateway-config-select.scss similarity index 100% rename from ui/src/app/components/gateWay/gateway-config-select.scss rename to ui/src/app/components/gateway/gateway-config-select.scss diff --git a/ui/src/app/components/gateWay/gateway-config-select.tpl.html b/ui/src/app/components/gateway/gateway-config-select.tpl.html similarity index 76% rename from ui/src/app/components/gateWay/gateway-config-select.tpl.html rename to ui/src/app/components/gateway/gateway-config-select.tpl.html index 57c89e5e4a..9a7f6ed655 100644 --- a/ui/src/app/components/gateWay/gateway-config-select.tpl.html +++ b/ui/src/app/components/gateway/gateway-config-select.tpl.html @@ -16,14 +16,14 @@ -->
- - {{item}} + {{item.name}}
@@ -41,14 +41,13 @@ gateway.no-gateway-found
- gateway.no-gateway-matching - gateway.create-new-gateway + gateway.no-gateway-matching + gateway.create-new-gateway
-
-
Test
+
+
gateway.gateway-name-required
diff --git a/ui/src/app/components/gateway/gateway-config.directive.js b/ui/src/app/components/gateway/gateway-config.directive.js new file mode 100644 index 0000000000..120a6d3e15 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config.directive.js @@ -0,0 +1,170 @@ +/* + * Copyright © 2016-2020 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. + */ +import './gateway-config.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayConfigTemplate from './gateway-config.tpl.html'; +import gatewayConfigDialogTemplate from './gateway-config-dialog.tpl.html'; +import beautify from "js-beautify"; + +/* eslint-enable import/no-unresolved, import/default */ +const js_beautify = beautify.js; + +export default angular.module('thingsboard.directives.gatewayConfig', []) + .directive('tbGatewayConfig', GatewayConfig) + .name; + +/*@ngInject*/ +function GatewayConfig() { + return { + restrict: "E", + scope: true, + bindToController: { + disabled: '=ngDisabled', + gatewayConfig: '=', + changeAlignment: '=', + theForm: '=' + }, + controller: GatewayConfigController, + controllerAs: 'vm', + templateUrl: gatewayConfigTemplate + }; +} + +/*@ngInject*/ +function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types) { + let vm = this; + vm.types = types; + + vm.removeConnector = (index) => { + if (index > -1) { + vm.gatewayConfig.splice(index, 1); + } + }; + + vm.addNewConnector = () => { + vm.gatewayConfig.push({ + enabled: false, + configType: '', + config: {}, + name: '' + }); + }; + + vm.openConfigDialog = ($event, index, config, typeName) => { + if ($event) { + $event.stopPropagation(); + } + $mdDialog.show({ + controller: GatewayDialogController, + controllerAs: 'vm', + templateUrl: gatewayConfigDialogTemplate, + parent: angular.element($document[0].body), + locals: { + config: config, + typeName: typeName + }, + targetEvent: $event, + fullscreen: true, + multiple: true, + }).then(function (config) { + if (config && index > -1) { + vm.gatewayConfig[index].config = config; + } + }); + + }; + + vm.changeConnectorType = (connector) => { + for (let gatewayConfigTypeKey in types.gatewayConfigType) { + if (types.gatewayConfigType[gatewayConfigTypeKey].value === connector.configType) { + if (!connector.name) { + connector.name = generateConnectorName(types.gatewayConfigType[gatewayConfigTypeKey].name, 0); + break; + } + } + } + }; + + vm.changeConnectorName = (connector, currentConnectorIndex) => { + connector.name = validateConnectorName(connector.name, 0, currentConnectorIndex); + }; + + function generateConnectorName(name, index) { + let newKeyName = index ? name + index : name; + let indexRes = vm.gatewayConfig.findIndex((element) => element.name === newKeyName); + return indexRes === -1 ? newKeyName : generateConnectorName(name, ++index); + } + + function validateConnectorName(name, index, currentConnectorIndex) { + for (let i = 0; i < vm.gatewayConfig.length; i++) { + let nameEq = (index === 0) ? name : name + index; + if (i !== currentConnectorIndex && vm.gatewayConfig[i].name === nameEq) { + index++; + validateConnectorName(name, index, currentConnectorIndex); + } + } + return (index === 0) ? name : name + index; + } + + vm.validateJSON = (config) => { + return angular.equals({}, config); + }; +} + +/*@ngInject*/ +function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) { + let vm = this; + vm.config = js_beautify(angular.toJson(config), {indent_size: 4}); + vm.typeName = typeName; + vm.configAreaOptions = { + useWrapMode: true, + mode: 'json', + advanced: { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }, + onLoad: function (_ace) { + _ace.$blockScrolling = 1; + } + }; + + vm.validateConfig = (model, editorName) => { + if (model && model.length) { + try { + angular.fromJson(model); + $scope.theForm[editorName].$setValidity('config', true); + } catch (e) { + $scope.theForm[editorName].$setValidity('config', false); + } + } + }; + + vm.save = () => { + $mdDialog.hide(angular.fromJson(vm.config)); + }; + + vm.cancel = () => { + $mdDialog.hide(); + }; + + vm.beautifyJson = () => { + vm.config = js_beautify(vm.config, {indent_size: 4}); + }; +} + diff --git a/ui/src/app/components/gateWay/gateway-config.scss b/ui/src/app/components/gateway/gateway-config.scss similarity index 85% rename from ui/src/app/components/gateWay/gateway-config.scss rename to ui/src/app/components/gateway/gateway-config.scss index f128db8f21..45d9532927 100644 --- a/ui/src/app/components/gateWay/gateway-config.scss +++ b/ui/src/app/components/gateway/gateway-config.scss @@ -56,20 +56,20 @@ .tb-json-toolbar{ height: 40px; } +} - .tb-json-panel { - height: calc(100% - 80px); - margin-left: 15px; - border: 1px solid #c0c0c0; +.tb-json-panel { + height: calc(100% - 80px); + margin-left: 15px; + border: 1px solid #c0c0c0; - .tb-json-input { - width: 100%; - min-width: 400px; - height: 100%; + .tb-json-input { + width: 100%; + min-width: 400px; + height: 100%; - &:not(.fill-height) { - min-height: 200px; - } + &:not(.fill-height) { + min-height: 200px; } } } diff --git a/ui/src/app/components/gateway/gateway-config.tpl.html b/ui/src/app/components/gateway/gateway-config.tpl.html new file mode 100644 index 0000000000..eef7c737e4 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config.tpl.html @@ -0,0 +1,81 @@ + +
+
+
+ + +
+
+ + + + + {{configType.value}} + + +
+
gateway.connector-type-required
+
+
+ + +
+
gateway.connector-name-required
+
+
+
+
+ + more_horiz + + {{ 'gateway.update-config' | translate }} + + + + close + + {{ 'gateway.delete' | translate }} + + +
+
+ {{'gateway.no-connectors'}} +
+ + + {{ 'gateway.connector-add' | translate }} + + action.add + +
+
diff --git a/ui/src/app/components/gateway/gateway-form.directive.js b/ui/src/app/components/gateway/gateway-form.directive.js new file mode 100644 index 0000000000..1e2e02ce24 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-form.directive.js @@ -0,0 +1,467 @@ +/* + * Copyright © 2016-2020 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. + */ +import './gateway-form.scss'; +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayFormTemplate from './gateway-form.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.directives.gatewayForm', []) + .directive('tbGatewayForm', GatewayForm) + .name; + +/*@ngInject*/ +function GatewayForm() { + return { + restrict: "E", + scope: true, + bindToController: { + formId: '=', + ctx: '=' + }, + controller: GatewayFormController, + controllerAs: 'vm', + templateUrl: gatewayFormTemplate + }; +} + +/*@ngInject*/ +function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q, entityService, utils, $translate) { + let vm = this; + const currentConfigurationAttribute = "current_configuration"; + const configurationDraftsAttribute = "configuration_drafts"; + const configurationAttribute = "configuration"; + const remoteLoggingLevelAttribute = "RemoteLoggingLevel"; + + const templateLogsConfig = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"'; + + vm.types = types; + + vm.configurations = { + gateway: '', + host: $document[0].domain, + port: 1883, + remoteConfiguration: true, + accessToken: '', + storageType: "memoryStorage", + readRecordsCount: 100, + maxRecordsCount: 10000, + dataFolderPath: './data/', + maxFilesCount: 5, + securityType: "accessToken", + caCertPath: '/etc/thingsboard-gateway/ca.pem', + privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem', + certPath: '/etc/thingsboard-gateway/certificate.pem', + connectors: [], + remoteLoggingLevel: "DEBUG", + remoteLoggingPathToLogs: './logs/' + }; + + let archiveFileName = ''; + let gatewayNameExists = ''; + let successfulSaved = ''; + + vm.securityTypes = [{ + name: 'gateway.security-types.access-token', + value: 'accessToken' + }, { + name: 'gateway.security-types.tls', + value: 'tls' + }]; + + vm.storageTypes = [{ + name: 'gateway.storage-types.memory-storage', + value: 'memoryStorage' + }, { + name: 'gateway.storage-types.file-storage', + value: 'fileStorage' + }]; + + $scope.$watch('vm.ctx', function () { + if (vm.ctx ) { + vm.settings = vm.ctx.settings; + vm.widgetConfig = vm.ctx.widgetConfig; + initializeConfig(); + } + }); + + $scope.$on('gateway-form-resize', function (event, formId) { + if (vm.formId == formId) { + updateWidgetDisplaying(); + } + }); + + function updateWidgetDisplaying() { + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425); + } + + function initWidgetSettings() { + let widgetTitle; + if (vm.settings.widgetTitle && vm.settings.widgetTitle.length) { + widgetTitle = utils.customTranslation(vm.settings.widgetTitle, vm.settings.widgetTitle); + } else { + widgetTitle = $translate.instant('gateway.gateway'); + } + vm.ctx.widgetTitle = widgetTitle; + + archiveFileName = vm.settings.archiveFileName && vm.settings.archiveFileName.length ? vm.settings.archiveFileName : 'gatewayConfiguration'; + gatewayNameExists = utils.customTranslation(vm.settings.deviceNameExist, vm.settings.deviceNameExist) || $translate.instant('gateway.gateway-exists'); + successfulSaved = utils.customTranslation(vm.settings.successfulSave, vm.settings.successfulSave) || $translate.instant('gateway.gateway-saved'); + } + + function initializeConfig() { + updateWidgetDisplaying(); + initWidgetSettings(); + getGatewaysList(true); + } + + vm.getAccessToken = (deviceId) => { + if (deviceId.id) { + getDeviceCredentials(deviceId.id); + } + }; + + vm.collapsePanel = function (panelId) { + $mdExpansionPanel(panelId).collapse(); + }; + + function getDeviceCredentials(deviceId) { + return deviceService.getDeviceCredentials(deviceId).then( + (deviceCredentials) => { + vm.configurations.accessToken = deviceCredentials.credentialsId; + getAttributes(); + } + ); + } + + vm.createDevice = (deviceObj) => { + deviceService.findByName(deviceObj.name, {ignoreErrors: true}) + .then( + function () { + toast.showError(gatewayNameExists, angular.element('.gateway-form'),'top left'); + }, + function () { + if(vm.settings.gatewayType && vm.settings.gatewayType.length){ + deviceObj.type = vm.settings.gatewayType; + } + deviceService.saveDevice(deviceObj).then( + (device) => { + getDeviceCredentials(device.id.id).then(() =>{ + getGatewaysList(); + }); + } + ); + }); + }; + + vm.saveAttributeConfig = () => { + $q.all([ + saveAttribute(configurationAttribute, $window.btoa(angular.toJson(getGatewayConfigJSON())), types.attributesScope.shared.value), + saveAttribute(configurationDraftsAttribute, $window.btoa(angular.toJson(getDraftConnectorJSON())), types.attributesScope.server.value), + saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value) + ]).then(() =>{ + toast.showSuccess(successfulSaved, 2000, angular.element('.gateway-form'),'top left'); + }) + }; + + function getAttributes() { + let promises = []; + promises.push(getAttribute(currentConfigurationAttribute, types.attributesScope.client.value)); + promises.push(getAttribute(configurationDraftsAttribute, types.attributesScope.server.value)); + promises.push(getAttribute(remoteLoggingLevelAttribute, types.attributesScope.shared.value)); + $q.all(promises).then((response) => { + processCurrentConfiguration(response[0]); + processConfigurationDrafts(response[1]); + processLoggingLevel(response[2]); + }); + } + + function getAttribute(attributeName, attributeScope) { + return attributeService.getEntityAttributesValues(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributeName); + } + + function saveAttribute(attributeName, attributeValue, attributeScope) { + let attributes = [{ + key: attributeName, + value: attributeValue + }]; + return attributeService.saveEntityAttributes(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributes); + } + + vm.exportConfig = () => { + let filesZip = {}; + filesZip["tb_gateway.yaml"] = generateYAMLConfigurationFile(); + generateConfigConnectorFiles(filesZip); + generateLogConfigFile(filesZip); + importExport.exportJSZip(filesZip, archiveFileName); + saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); + }; + + function generateYAMLConfigurationFile() { + let config; + config = 'thingsboard:\n'; + config += ' host: ' + vm.configurations.host + '\n'; + config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n'; + config += ' port: ' + vm.configurations.port + '\n'; + config += ' security:\n'; + if (vm.configurations.securityType === 'accessToken') { + config += ' access-token: ' + vm.configurations.accessToken + '\n'; + } else if (vm.configurations.securityType === 'tls') { + config += ' ca_cert: ' + vm.configurations.caCertPath + '\n'; + config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n'; + config += ' cert: ' + vm.configurations.certPath + '\n'; + } + config += 'storage:\n'; + if (vm.configurations.storageType === 'memoryStorage') { + config += ' type: memory\n'; + config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n'; + } else if (vm.configurations.storageType === 'fileStorage') { + config += ' type: file\n'; + config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n'; + config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n'; + config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n'; + } + config += 'connectors:\n'; + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + config += ' -\n'; + config += ' name: ' + vm.configurations.connectors[i].name + '\n'; + config += ' type: ' + vm.configurations.connectors[i].configType + '\n'; + config += ' configuration: ' + generateFileName(vm.configurations.connectors[i].name) + '\n'; + } + } + return config; + } + + function generateConfigConnectorFiles(fileZipAdd) { + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + fileZipAdd[generateFileName(vm.configurations.connectors[i].name)] = angular.toJson(vm.configurations.connectors[i].config); + } + } + } + + function generateLogConfigFile(fileZipAdd) { + fileZipAdd["logs.conf"] = getLogsConfig(); + } + + function getLogsConfig() { + return templateLogsConfig + .replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel) + .replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs); + } + + function getGatewayConfigJSON() { + let gatewayConfig = {}; + gatewayConfig["thingsboard"] = gatewayMainConfigJSON(); + gatewayConnectorConfigJSON(gatewayConfig); + return gatewayConfig; + } + + function gatewayMainConfigJSON() { + let configuration = {}; + + let thingsBoard = {}; + thingsBoard.host = vm.configurations.host; + thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration; + thingsBoard.port = vm.configurations.port; + let security = {}; + if (vm.configurations.securityType === 'accessToken') { + security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : "" + } else { + security.caCert = vm.configurations.caCertPath; + security.privateKey = vm.configurations.privateKeyPath; + security.cert = vm.configurations.certPath; + } + thingsBoard.security = security; + configuration.thingsboard = thingsBoard; + + let storage = {}; + if (vm.configurations.storageType === 'memoryStorage') { + storage.type = "memory"; + storage.read_records_count = vm.configurations.readRecordsCount; + storage.max_records_count = vm.configurations.maxRecordsCount; + } else if (vm.configurations.storageType === 'fileStorage') { + storage.type = "file"; + storage.data_folder_path = vm.configurations.dataFolderPath; + storage.max_file_count = vm.configurations.maxFilesCount; + storage.max_read_records_count = vm.configurations.readRecordsCount; + storage.max_records_per_file = vm.configurations.maxRecordsCount; + } + configuration.storage = storage; + + let connectors = []; + for (let i = 0; i < vm.configurations.connectors.length; i++) { + if (vm.configurations.connectors[i].enabled) { + let connector = { + configuration: generateFileName(vm.configurations.connectors[i].name), + name: vm.configurations.connectors[i].name, + type: vm.configurations.connectors[i].configType + }; + connectors.push(connector); + } + } + configuration.connectors = connectors; + + configuration.logs = $window.btoa(getLogsConfig()); + + return configuration; + } + + function gatewayConnectorConfigJSON(gatewayConfiguration) { + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + let typeConnector = vm.configurations.connectors[i].configType; + if(!angular.isArray(gatewayConfiguration[typeConnector])){ + gatewayConfiguration[typeConnector] = []; + } + + let connectorConfig = { + name: vm.configurations.connectors[i].name, + config: vm.configurations.connectors[i].config + }; + gatewayConfiguration[typeConnector].push(connectorConfig); + } + } + } + + function getDraftConnectorJSON() { + let draftConnector = {}; + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (!vm.configurations.connectors[i].enabled) { + let connector = { + connector: vm.configurations.connectors[i].configType, + config: vm.configurations.connectors[i].config + }; + draftConnector[vm.configurations.connectors[i].name] = connector; + } + } + return draftConnector; + } + + function getGatewaysList(firstInit) { + vm.gateways = []; + entityService.getEntitiesByNameFilter(types.entityType.device, "", -1).then((devices) => { + for (let i = 0; i < devices.length; i++) { + const device = devices[i]; + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { + vm.gateways.push(device); + if (firstInit && vm.gateways.length && device.name === vm.gateways[0].name) { + vm.configurations.gateway = device; + vm.getAccessToken(device.id); + } + } + } + }); + } + + function processCurrentConfiguration(response) { + if (response.length > 0) { + vm.configurations.connectors = []; + let attribute = angular.fromJson($window.atob(response[0].value)); + for (var attributeKey in attribute) { + let keyValue = attribute[attributeKey]; + if (attributeKey === "thingsboard") { + if (keyValue !== null && Object.keys(keyValue).length > 0) { + setConfigGateway(keyValue); + } + } else { + for (let connectorType in keyValue) { + let name = "No name"; + if (Object.prototype.hasOwnProperty.call(keyValue[connectorType], 'name')) { + name = keyValue[connectorType].name ; + } + let connector = { + enabled: true, + configType: attributeKey, + config: keyValue[connectorType].config, + name: name + }; + vm.configurations.connectors.push(connector); + } + } + } + } + } + + function processConfigurationDrafts(response) { + if (response.length > 0) { + let attribute = angular.fromJson($window.atob(response[0].value)); + for (let key in attribute) { + let connector = { + enabled: false, + configType: attribute[key].connector, + config: attribute[key].config, + name: key + }; + vm.configurations.connectors.push(connector); + } + } + } + + function processLoggingLevel(response) { + if (response.length > 0) { + if (vm.types.gatewayLogLevel[response[0].value.toLowerCase()]) { + vm.configurations.remoteLoggingLevel = response[0].value.toUpperCase(); + } + } else { + vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug; + } + } + + function setConfigGateway(keyValue) { + if (Object.prototype.hasOwnProperty.call(keyValue, 'thingsboard')) { + vm.configurations.host = keyValue.thingsboard.host; + vm.configurations.port = keyValue.thingsboard.port; + vm.configurations.remoteConfiguration = keyValue.thingsboard.remoteConfiguration; + if (Object.prototype.hasOwnProperty.call(keyValue.thingsboard.security, 'accessToken')) { + vm.configurations.securityType = 'accessToken'; + vm.configurations.accessToken = keyValue.thingsboard.security.accessToken; + } else { + vm.configurations.securityType = 'tls'; + vm.configurations.caCertPath = keyValue.thingsboard.security.caCert; + vm.configurations.privateKeyPath = keyValue.thingsboard.security.private_key; + vm.configurations.certPath = keyValue.thingsboard.security.cert; + } + } + + if (Object.prototype.hasOwnProperty.call(keyValue, 'storage') && Object.prototype.hasOwnProperty.call(keyValue.storage, 'type')) { + if (keyValue.storage.type === 'memory') { + vm.configurations.storageType = 'memoryStorage'; + vm.configurations.readRecordsCount = keyValue.storage.read_records_count; + vm.configurations.maxRecordsCount = keyValue.storage.max_records_count; + } else if (keyValue.storage.type === 'file') { + vm.configurations.storageType = 'fileStorage'; + vm.configurations.dataFolderPath = keyValue.storage.data_folder_path; + vm.configurations.maxFilesCount = keyValue.storage.max_file_count; + vm.configurations.readRecordsCount = keyValue.storage.read_records_count; + vm.configurations.maxRecordsCount = keyValue.storage.max_records_count; + } + } + } + + function generateFileName(fileName) { + return fileName.replace("_", "") + .replace("-", "") + .replace(/^\s+|\s+/g, '') + .toLowerCase() + '.json'; + } +} + + diff --git a/ui/src/app/components/gateWay/gateway-form.scss b/ui/src/app/components/gateway/gateway-form.scss similarity index 90% rename from ui/src/app/components/gateWay/gateway-form.scss rename to ui/src/app/components/gateway/gateway-form.scss index a5e7bc8b44..f6851c7688 100644 --- a/ui/src/app/components/gateWay/gateway-form.scss +++ b/ui/src/app/components/gateway/gateway-form.scss @@ -14,7 +14,9 @@ * limitations under the License. */ .gateway-form{ + height: 100%; padding: 5px 5px 0; + background-color: transparent; .gateway-form-row{ md-input-container{ @@ -30,11 +32,11 @@ } } + .security-type { + margin-top: 18px; + } + .form-action-buttons{ padding-top: 8px; } } - -.security-type { - margin-top: 38px; -} diff --git a/ui/src/app/components/gateway/gateway-form.tpl.html b/ui/src/app/components/gateway/gateway-form.tpl.html new file mode 100644 index 0000000000..abe030d543 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-form.tpl.html @@ -0,0 +1,227 @@ + + +
+ + + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + + + + + + + {{securityType.name | translate}} + + + +
+ + + +
+
gateway.thingsboard-host-required
+
+
+ + + +
+
gateway.thingsboard-port-required
+
gateway.thingsboard-port-max
+
gateway.thingsboard-port-min
+
gateway.thingsboard-port-pattern
+
+
+
+
+ + + + + + + + + + + + +
+ + {{ 'gateway.remote' | translate }} + +
+ + + + + {{logLevel}} + + + + + + +
+
gateway.path-logs-required
+
+
+
+
+
+
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + + + + + {{storageType.name | translate}} + + + + +
+ + + +
+
gateway.storage-pack-size-required
+
gateway.storage-pack-size-min
+
gateway.storage-pack-size-pattern
+
+
+ + + + +
+
gateway.storage-max-records-required
+
gateway.storage-max-records-min
+
gateway.storage-max-records-pattern
+
+
+
+ +
+ + + +
+
gateway.storage-max-files-required
+
gateway.storage-max-files-min
+
gateway.storage-max-files-pattern
+
+
+ + + + +
+
gateway.storage-path-required
+
+
+
+
+
+
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + + + +
+
+
+
+ + {{'action.download' | translate }} + {{'gateway.download-tip' | translate }} + + + + {{'action.save' | translate }} + {{'gateway.save-tip' | translate }} + +
+
+
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 6220fc3c6a..b5dbfa6ef3 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -29,7 +29,7 @@ import * as JSZip from 'jszip'; export default function ImportExport($log, $translate, $q, $mdDialog, $document, $http, itembuffer, utils, types, $rootScope, dashboardUtils, entityService, dashboardService, ruleChainService, widgetService, toast, attributeService) { - const JSZIP_TYPE = { + const ZIP_TYPE = { mimeType: 'application/zip', extension: 'zip' }; @@ -989,29 +989,19 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, dialogElement[0].style.width = dialogElement[0].offsetWidth + 2 + "px"; } - /** - * - * @param data - * @param filename - * Warn data !!! Not object, if object, then object convert from object to format txt - * Example: data = {keyNameFile1: valueFile1, - * keyNameFile2: valueFile2...} - * fileName - name file of the arhiv - */ function exportJSZip(data, filename) { let jsZip = new JSZip(); for (let keyName in data) { let valueData = data[keyName]; jsZip.file(keyName, valueData); } - jsZip.generateAsync({type: "Blob"}).then(function (content) { - downloadFile(content, filename, JSZIP_TYPE); + jsZip.generateAsync({type: "blob"}).then(function (content) { + downloadFile(content, filename, ZIP_TYPE); }); } function downloadFile(data, filename, fileType) { - console.log("downloadFile", data, filename, fileType); // eslint-disable-line if (!filename) { filename = 'download'; } diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js index d674a1fdd3..a00baa5199 100644 --- a/ui/src/app/layout/index.js +++ b/ui/src/app/layout/index.js @@ -30,9 +30,9 @@ import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardNavTree from '../components/nav-tree.directive'; import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive'; import thingsboardKvMap from '../components/kv-map.directive'; -import thingsboardGatewayConfig from '../components/gateWay/gateway-config.directive'; -import thingsboardGatewayConfigSelect from '../components/gateWay/gateway-config-select.directive'; -import thingsboardGatewayForm from '../components/gateWay/gateway-form.directive'; +import thingsboardGatewayConfig from '../components/gateway/gateway-config.directive'; +import thingsboardGatewayConfigSelect from '../components/gateway/gateway-config-select.directive'; +import thingsboardGatewayForm from '../components/gateway/gateway-form.directive'; import thingsboardJsonObjectEdit from '../components/json-object-edit.directive'; import thingsboardJsonContent from '../components/json-content.directive'; diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 5acd0f81e5..48bff7d6cd 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1126,56 +1126,78 @@ "function": "Function" }, "gateway": { - "key": "Key configuration", - "value": "Value configuration", - "remove-entry": "Remove configuration", "add-entry": "Add configuration", - "no-data": "No configurations", - "gateway-required": "Gateway is required.", - "gateway-name": "Gateway name", + "connector-add": "Add new connector", + "connector-enabled": "Enable connector", + "connector-name": "Connector name", + "connector-name-required": "Connector name is required.", + "connector-type": "Connector type", + "connector-type-required": "Connector type is required.", + "connectors": "Connectors configuration", "create-new-gateway": "Create a new gateway", "create-new-gateway-text": "Are you sure you want create a new gateway with name: '{{gatewayName}}'?", + "delete": "Delete configuration", + "download-tip": "Download configuration file", + "gateway": "Gateway", + "gateway-exists": "Device with same name is already exists.", + "gateway-name": "Gateway name", + "gateway-name-required": "Gateway name is required.", + "gateway-saved": "Gateway configuration successfully saved.", + "json-parse": "Not valid JSON.", + "json-required": "Field cannot be empty.", + "no-connectors": "No connectors", + "no-data": "No configurations", + "no-gateway-found": "No gateway found.", "no-gateway-matching": " '{{item}}' not found.", - "thingsboard": "ThingsBoard", - "connectors": "Connectors configuration", - "thingsboard-host": "ThingsBoard Host", - "thingsboard-port": "ThingsBoard Port", + "path-logs": "Path to log files", + "path-logs-required": "Path is required.", + "remote": "Remote configuration", + "remote-logging-level": "Logging level", + "remove-entry": "Remove configuration", + "save-tip": "Save configuration file", "security-type": "Security type", - "tls-path-ca-certificate": "Path to CA certificate on gateway:", - "tls-path-private-key": "Path to private key on gateway:", - "tls-path-client-certificate": "Path to client certificate on gateway:", + "security-types": { + "access-token": "Access Token", + "tls": "TLS" + }, "storage": "Storage", + "storage-max-file-records": "Maximum records in file", + "storage-max-files": "Maximum number of files", + "storage-max-files-min": "Minimum number is 1.", + "storage-max-files-pattern": "Number is not valid.", + "storage-max-files-required": "Number is required.", + "storage-max-records": "Maximum records in storage", + "storage-max-records-min": "Minimum number of records is 1.", + "storage-max-records-pattern": "Number is not valid.", + "storage-max-records-required": "Maximum records is required.", + "storage-pack-size": "Maximum event pack size", + "storage-pack-size-min": "Minimum number is 1.", + "storage-pack-size-pattern": "Number is not valid.", + "storage-pack-size-required": "Maximum event pack size is required.", + "storage-path": "Storage path", + "storage-path-required": "Storage path is required.", "storage-type": "Storage type", - "storage-read-time": "Read records per time:", - "storage-max-time": "Maximum records per time:", - "storage-max-files": "Maximum files:", - "storage-data-path": "Data folder path:", - "download-tip": "Download configuration file", - "save-tip": "Save configuration file", - "remote-tip": "Allow remote configuration", - "remote": "Remote configuration", - "remote-logging-level": "Logging level", - "remote-logging-path-logs": "Path to logs", - "connector-type": "Connector type", - "update-config": "Add/update config JSON", - "delete": "Delete configuration", - "title-connectors-json": "Connector {{typeName}} configuration", - "json-required": "Config json is required for gateway config.", - "json-parse": "Unable to parse config json for gateway config.", + "storage-types": { + "file-storage": "File storage", + "memory-storage": "Memory storage" + }, + "thingsboard": "ThingsBoard", + "thingsboard-host": "ThingsBoard host", + "thingsboard-host-required": "Host is required.", + "thingsboard-port": "ThingsBoard port", + "thingsboard-port-max": "Maximum port number is 65535.", + "thingsboard-port-min": "Minimum port number is 1.", + "thingsboard-port-pattern": "Port is not valid.", + "thingsboard-port-required": "Port is required.", "tidy": "Tidy", "tidy-tip": "Tidy config JSON", - "transformer-json-config": "JSON for the config*", + "title-connectors-json": "Connector {{typeName}} configuration", + "tls-path-ca-certificate": "Path to CA certificate on gateway", + "tls-path-client-certificate": "Path to client certificate on gateway", + "tls-path-private-key": "Path to private key on gateway", "toggle-fullscreen": "Toggle fullscreen", - "add-connectors": "Add new connectors", - "no-connectors": "No connectors", - "enabled": "Enabled", - "name": "Name", - "no-gateway-found": "No gateway found.", - "gateway": "Gateway", - "keyval-save-err": "Save config error", - "keyval-name-err": "Please add Name", - "keyval-type-err": "Please add Connector type", - "keyval-config-err": "Please add configuration JSON" + "transformer-json-config": "Configuration JSON*", + "update-config": "Add/update configuration JSON" }, "grid": { "delete-item-title": "Are you sure you want to delete this item?", From 3955600a9ca170f1dab3c0a575a05587d685bad7 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 6 Feb 2020 09:08:47 +0200 Subject: [PATCH 187/261] bug fixes & improvements / sql-timeseries (#2382) * fixed the partion date extracting * fix imports * ts-keys dictionary for latest, hsqldb * removed AbstractSimpleSqlTimeseriesDao class & fix beanCreationException in ThingsboardInstallService * timescale-db upgrade added * added postgreSQL upgrade * fix logging * refactoring timeseries-dao implementation --- .../upgrade/2.4.3/schema_update_psql_ts.sql | 86 ++++++- .../2.4.3/schema_update_timescale_ts.sql | 213 ++++++++++++++++++ .../install/ThingsboardInstallService.java | 2 +- .../install/PsqlTsDatabaseUpgradeService.java | 16 +- .../SqlTimescaleDatabaseUpgradeService.java | 147 ++++++++++++ .../src/main/resources/thingsboard.yml | 4 - .../server/dao/util/SqlTsAnyDao.java | 22 ++ .../server/dao/util/TimescaleDBTsDao.java | 0 .../server/dao/HsqlTsDaoConfig.java | 4 +- .../thingsboard/server/dao/JpaDaoConfig.java | 1 - .../server/dao/TimescaleDaoConfig.java | 2 + .../dao/model/sql/AbstractTsKvEntity.java | 37 ++- .../sqlts/dictionary/TsKvDictionary.java | 2 +- .../model/sqlts/hsql/TsKvCompositeKey.java | 6 +- .../dao/model/sqlts/hsql/TsKvEntity.java | 29 +-- .../sqlts/latest/TsKvLatestCompositeKey.java | 6 +- .../model/sqlts/latest/TsKvLatestEntity.java | 84 ++++--- .../dao/model/sqlts/psql/TsKvEntity.java | 27 +-- .../sqlts/timescale/TimescaleTsKvEntity.java | 26 +-- ...ava => AbstractPsqlHsqlTimeseriesDao.java} | 96 +++----- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 96 ++++++-- .../dictionary/TsKvDictionaryRepository.java | 4 +- .../hsql/HsqlTimeseriesInsertRepository.java | 32 ++- .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 131 ++++++----- .../dao/sqlts/hsql/TsKvHsqlRepository.java | 76 +++---- .../latest/HsqlLatestInsertRepository.java | 32 ++- .../latest/PsqlLatestInsertRepository.java | 48 ++-- .../latest/SearchTsKvLatestRepository.java | 45 ++++ .../sqlts/latest/TsKvLatestRepository.java | 4 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 122 +++++----- .../timescale/TimescaleTimeseriesDao.java | 198 ++++++---------- .../main/resources/sql/schema-timescale.sql | 9 +- dao/src/main/resources/sql/schema-ts-hsql.sql | 22 +- dao/src/main/resources/sql/schema-ts-psql.sql | 19 +- 34 files changed, 1037 insertions(+), 611 deletions(-) create mode 100644 application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql create mode 100644 application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlTsAnyDao.java rename {dao => common/dao-api}/src/main/java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java (100%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{AbstractSimpleSqlTimeseriesDao.java => AbstractPsqlHsqlTimeseriesDao.java} (56%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql index 07e0b51511..add03ed8f7 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql @@ -14,7 +14,7 @@ -- limitations under the License. -- --- load function check_version() +-- select check_version(); CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ DECLARE @@ -38,9 +38,9 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; --- load function create_partition_table() +-- select create_partition_ts_kv_table(); -CREATE OR REPLACE FUNCTION create_partition_table() RETURNS VOID AS $$ +CREATE OR REPLACE FUNCTION create_partition_ts_kv_table() RETURNS VOID AS $$ BEGIN ALTER TABLE ts_kv @@ -59,8 +59,32 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; +-- select create_new_ts_kv_latest_table(); --- load function create_partitions() +CREATE OR REPLACE FUNCTION create_new_ts_kv_latest_table() RETURNS VOID AS $$ + +BEGIN + ALTER TABLE ts_kv_latest + RENAME TO ts_kv_latest_old; + ALTER TABLE ts_kv_latest_old + RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old; + CREATE TABLE IF NOT EXISTS ts_kv_latest + ( + LIKE ts_kv_latest_old + ); + ALTER TABLE ts_kv_latest + DROP COLUMN entity_type; + ALTER TABLE ts_kv_latest + ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; + ALTER TABLE ts_kv_latest + ALTER COLUMN key TYPE integer USING key::integer; + ALTER TABLE ts_kv_latest + ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key); +END; +$$ LANGUAGE 'plpgsql'; + + +-- select create_partitions(); CREATE OR REPLACE FUNCTION create_partitions() RETURNS VOID AS $$ @@ -89,7 +113,7 @@ BEGIN END; $$ language 'plpgsql'; --- load function create_ts_kv_dictionary_table() +-- select create_ts_kv_dictionary_table(); CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ @@ -103,7 +127,7 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; --- load function insert_into_dictionary() +-- select insert_into_dictionary(); CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS $$ @@ -128,7 +152,7 @@ BEGIN END; $$ language 'plpgsql'; --- load function insert_into_ts_kv() +-- select insert_into_ts_kv(); CREATE OR REPLACE FUNCTION insert_into_ts_kv() RETURNS void AS $$ @@ -176,4 +200,52 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; +-- select insert_into_ts_kv_latest(); + +CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS +$$ +DECLARE + insert_size CONSTANT integer := 10000; + insert_counter integer DEFAULT 0; + insert_record RECORD; + insert_cursor CURSOR FOR SELECT CONCAT(first, '-', second, '-1', third, '-', fourth, '-', fifth)::uuid AS entity_id, + substrings.key AS key, + substrings.ts AS ts, + substrings.bool_v AS bool_v, + substrings.str_v AS str_v, + substrings.long_v AS long_v, + substrings.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first, + SUBSTRING(entity_id, 4, 4) AS second, + SUBSTRING(entity_id, 1, 3) AS third, + SUBSTRING(entity_id, 16, 4) AS fourth, + SUBSTRING(entity_id, 20) AS fifth, + key_id AS key, + ts, + bool_v, + str_v, + long_v, + dbl_v + FROM ts_kv_latest_old + INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS substrings; +BEGIN + OPEN insert_cursor; + LOOP + insert_counter := insert_counter + 1; + FETCH insert_cursor INTO insert_record; + IF NOT FOUND THEN + RAISE NOTICE '% records have been inserted into the ts_kv_latest!',insert_counter - 1; + EXIT; + END IF; + INSERT INTO ts_kv_latest(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) + VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, + insert_record.long_v, insert_record.dbl_v); + IF MOD(insert_counter, insert_size) = 0 THEN + RAISE NOTICE '% records have been inserted into the ts_kv_latest!',insert_counter; + END IF; + END LOOP; + CLOSE insert_cursor; +END; +$$ LANGUAGE 'plpgsql'; + diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql new file mode 100644 index 0000000000..715acd96c6 --- /dev/null +++ b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql @@ -0,0 +1,213 @@ +-- +-- Copyright © 2016-2020 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. +-- + +-- select check_version(); + +CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ +DECLARE + current_version integer; + valid_version boolean; +BEGIN + RAISE NOTICE 'Check the current installed PostgreSQL version...'; + SELECT current_setting('server_version_num') INTO current_version; + IF current_version < 90600 THEN + valid_version := FALSE; + ELSE + valid_version := TRUE; + END IF; + IF valid_version = FALSE THEN + RAISE NOTICE 'Postgres version should be at least more than 9.6!'; + ELSE + RAISE NOTICE 'PostgreSQL version is valid!'; + RAISE NOTICE 'Schema update started...'; + END IF; + RETURN valid_version; +END; +$$ LANGUAGE 'plpgsql'; + +-- select create_tenant_ts_kv_table_copy(); + +CREATE OR REPLACE FUNCTION create_tenant_ts_kv_table_copy() RETURNS VOID AS $$ + +BEGIN + ALTER TABLE tenant_ts_kv + RENAME TO tenant_ts_kv_old; + CREATE TABLE IF NOT EXISTS tenant_ts_kv + ( + LIKE tenant_ts_kv_old + ); + ALTER TABLE tenant_ts_kv + ALTER COLUMN tenant_id TYPE uuid USING tenant_id::uuid; + ALTER TABLE tenant_ts_kv + ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; + ALTER TABLE tenant_ts_kv + ALTER COLUMN key TYPE integer USING key::integer; + ALTER TABLE tenant_ts_kv + ADD CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY(tenant_id, entity_id, key, ts); + ALTER INDEX idx_tenant_ts_kv RENAME TO idx_tenant_ts_kv_old; + ALTER INDEX tenant_ts_kv_ts_idx RENAME TO tenant_ts_kv_ts_idx_old; + PERFORM create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); + CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts); +END; +$$ LANGUAGE 'plpgsql'; + + +-- select create_ts_kv_latest_table(); + +CREATE OR REPLACE FUNCTION create_ts_kv_latest_table() RETURNS VOID AS $$ + +BEGIN + CREATE TABLE IF NOT EXISTS ts_kv_latest + ( + entity_id uuid NOT NULL, + key int NOT NULL, + ts bigint NOT NULL, + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision, + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) + ); +END; +$$ LANGUAGE 'plpgsql'; + + +-- select create_ts_kv_dictionary_table(); + +CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ + +BEGIN + CREATE TABLE IF NOT EXISTS ts_kv_dictionary + ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) + ); +END; +$$ LANGUAGE 'plpgsql'; + +-- select insert_into_dictionary(); + +CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS +$$ +DECLARE + insert_record RECORD; + key_cursor CURSOR FOR SELECT DISTINCT key + FROM tenant_ts_kv_old + ORDER BY key; +BEGIN + OPEN key_cursor; + LOOP + FETCH key_cursor INTO insert_record; + EXIT WHEN NOT FOUND; + IF NOT EXISTS(SELECT key FROM ts_kv_dictionary WHERE key = insert_record.key) THEN + INSERT INTO ts_kv_dictionary(key) VALUES (insert_record.key); + RAISE NOTICE 'Key: % has been inserted into the dictionary!',insert_record.key; + ELSE + RAISE NOTICE 'Key: % already exists in the dictionary!',insert_record.key; + END IF; + END LOOP; + CLOSE key_cursor; +END; +$$ language 'plpgsql'; + +-- select insert_into_tenant_ts_kv(); + +CREATE OR REPLACE FUNCTION insert_into_tenant_ts_kv() RETURNS void AS +$$ +DECLARE + insert_size CONSTANT integer := 10000; + insert_counter integer DEFAULT 0; + insert_record RECORD; + insert_cursor CURSOR FOR SELECT CONCAT(tenant_id_first, '-', tenant_id_second, '-1', tenant_id_third, '-', tenant_id_fourth, '-', tenant_id_fifth)::uuid AS tenant_id, + CONCAT(entity_id_first, '-', entity_id_second, '-1', entity_id_third, '-', entity_id_fourth, '-', entity_id_fifth)::uuid AS entity_id, + substrings.key AS key, + substrings.ts AS ts, + substrings.bool_v AS bool_v, + substrings.str_v AS str_v, + substrings.long_v AS long_v, + substrings.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(tenant_id, 8, 8) AS tenant_id_first, + SUBSTRING(tenant_id, 4, 4) AS tenant_id_second, + SUBSTRING(tenant_id, 1, 3) AS tenant_id_third, + SUBSTRING(tenant_id, 16, 4) AS tenant_id_fourth, + SUBSTRING(tenant_id, 20) AS tenant_id_fifth, + SUBSTRING(entity_id, 8, 8) AS entity_id_first, + SUBSTRING(entity_id, 4, 4) AS entity_id_second, + SUBSTRING(entity_id, 1, 3) AS entity_id_third, + SUBSTRING(entity_id, 16, 4) AS entity_id_fourth, + SUBSTRING(entity_id, 20) AS entity_id_fifth, + key_id AS key, + ts, + bool_v, + str_v, + long_v, + dbl_v + FROM tenant_ts_kv_old + INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS substrings; +BEGIN + OPEN insert_cursor; + LOOP + insert_counter := insert_counter + 1; + FETCH insert_cursor INTO insert_record; + IF NOT FOUND THEN + RAISE NOTICE '% records have been inserted into the new tenant_ts_kv table!',insert_counter - 1; + EXIT; + END IF; + INSERT INTO tenant_ts_kv(tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) + VALUES (insert_record.tenant_id, insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, + insert_record.long_v, insert_record.dbl_v); + IF MOD(insert_counter, insert_size) = 0 THEN + RAISE NOTICE '% records have been inserted into the new tenant_ts_kv table!',insert_counter; + END IF; + END LOOP; + CLOSE insert_cursor; +END; +$$ LANGUAGE 'plpgsql'; + +-- select insert_into_ts_kv_latest(); + +CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS +$$ +DECLARE + insert_size CONSTANT integer := 10000; + insert_counter integer DEFAULT 0; + latest_record RECORD; + insert_record RECORD; + insert_cursor CURSOR FOR SELECT + latest.key AS key, + latest.entity_id AS entity_id, + latest.ts AS ts + FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM tenant_ts_kv GROUP BY key, entity_id) AS latest; +BEGIN + OPEN insert_cursor; + LOOP + insert_counter := insert_counter + 1; + FETCH insert_cursor INTO latest_record; + IF NOT FOUND THEN + RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter - 1; + EXIT; + END IF; + SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM tenant_ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts; + INSERT INTO ts_kv_latest(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) + VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, insert_record.long_v, insert_record.dbl_v); + IF MOD(insert_counter, insert_size) = 0 THEN + RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter; + END IF; + END LOOP; + CLOSE insert_cursor; +END; +$$ LANGUAGE 'plpgsql'; 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 78906e855e..14d9ff821f 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -53,7 +53,7 @@ public class ThingsboardInstallService { @Autowired private DatabaseEntitiesUpgradeService databaseEntitiesUpgradeService; - @Autowired + @Autowired(required = false) private DatabaseTsUpgradeService databaseTsUpgradeService; @Autowired diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index 10b1e45231..8ce67b1a42 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -43,12 +43,15 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { private static final String CALL_REGEX = "call "; private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; - private static final String CREATE_PARTITION_TABLE = CALL_REGEX + "create_partition_table()"; + private static final String CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + "create_partition_ts_kv_table()"; + private static final String CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + "create_new_ts_kv_latest_table()"; private static final String CREATE_PARTITIONS = CALL_REGEX + "create_partitions()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_ts_kv()"; - private static final String DROP_OLD_TABLE = "DROP TABLE ts_kv_old;"; + private static final String INSERT_INTO_TS_KV_LATEST = CALL_REGEX + "insert_into_ts_kv_latest()"; + private static final String DROP_TABLE_TS_KV_OLD = "DROP TABLE ts_kv_old;"; + private static final String DROP_TABLE_TS_KV_LATEST_OLD = "DROP TABLE ts_kv_latest_old;"; @Value("${spring.datasource.url}") private String dbUrl; @@ -70,7 +73,6 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { log.info("Updating timeseries schema ..."); log.info("Load upgrade functions ..."); loadSql(conn); - log.info("Upgrade functions successfully loaded!"); boolean versionValid = checkVersion(conn); if (!versionValid) { log.info("PostgreSQL version should be at least more than 10!"); @@ -78,12 +80,15 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { } else { log.info("PostgreSQL version is valid!"); log.info("Updating schema ..."); - executeFunction(conn, CREATE_PARTITION_TABLE); + executeFunction(conn, CREATE_PARTITION_TS_KV_TABLE); executeFunction(conn, CREATE_PARTITIONS); executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); executeFunction(conn, INSERT_INTO_DICTIONARY); executeFunction(conn, INSERT_INTO_TS_KV); - dropOldTable(conn, DROP_OLD_TABLE); + executeFunction(conn, CREATE_NEW_TS_KV_LATEST_TABLE); + executeFunction(conn, INSERT_INTO_TS_KV_LATEST); + dropOldTable(conn, DROP_TABLE_TS_KV_OLD); + dropOldTable(conn, DROP_TABLE_TS_KV_LATEST_OLD); log.info("schema timeseries updated!"); } } @@ -97,6 +102,7 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); try { loadFunctions(schemaUpdateFile, conn); + log.info("Upgrade functions successfully loaded!"); } catch (Exception e) { log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage()); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java new file mode 100644 index 0000000000..aa592853e4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.TimescaleDBTsDao; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Types; + +@Service +@Profile("install") +@Slf4j +@TimescaleDBTsDao +@PsqlDao +public class SqlTimescaleDatabaseUpgradeService implements DatabaseTsUpgradeService { + + private static final String CALL_REGEX = "call "; + private static final String LOAD_FUNCTIONS_SQL = "schema_update_timescale_ts.sql"; + private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; + private static final String CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + "create_ts_kv_latest_table()"; + private static final String CREATE_TENANT_TS_KV_TABLE_COPY = CALL_REGEX + "create_tenant_ts_kv_table_copy()"; + private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; + private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; + private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_tenant_ts_kv()"; + private static final String INSERT_INTO_TS_KV_LATEST = CALL_REGEX + "insert_into_ts_kv_latest()"; + private static final String DROP_OLD_TS_KV_TABLE = "DROP TABLE tenant_ts_kv_old;"; + + @Value("${spring.datasource.url}") + private String dbUrl; + + @Value("${spring.datasource.username}") + private String dbUserName; + + @Value("${spring.datasource.password}") + private String dbPassword; + + @Autowired + private InstallScripts installScripts; + + @Override + public void upgradeDatabase(String fromVersion) throws Exception { + switch (fromVersion) { + case "2.4.3": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating timescale schema ..."); + log.info("Load upgrade functions ..."); + loadSql(conn); + boolean versionValid = checkVersion(conn); + if (!versionValid) { + log.info("PostgreSQL version should be at least more than 9.6!"); + log.info("Please upgrade your PostgreSQL and restart the script!"); + } else { + log.info("PostgreSQL version is valid!"); + log.info("Updating schema ..."); + executeFunction(conn, CREATE_TS_KV_LATEST_TABLE); + executeFunction(conn, CREATE_TENANT_TS_KV_TABLE_COPY); + executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); + executeFunction(conn, INSERT_INTO_DICTIONARY); + executeFunction(conn, INSERT_INTO_TS_KV); + executeFunction(conn, INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, DROP_OLD_TS_KV_TABLE); + log.info("schema timeseries updated!"); + } + } + break; + default: + throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); + } + } + + private void loadSql(Connection conn) { + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); + try { + loadFunctions(schemaUpdateFile, conn); + log.info("Upgrade functions successfully loaded!"); + } catch (Exception e) { + log.info("Failed to load Timescale upgrade functions due to: {}", e.getMessage()); + } + } + + private void loadFunctions(Path sqlFile, Connection conn) throws Exception { + String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } + + private boolean checkVersion(Connection conn) { + log.info("Check the current PostgreSQL version..."); + boolean versionValid = false; + try { + CallableStatement callableStatement = conn.prepareCall("{? = " + CHECK_VERSION + " }"); + callableStatement.registerOutParameter(1, Types.BOOLEAN); + callableStatement.execute(); + versionValid = callableStatement.getBoolean(1); + callableStatement.close(); + } catch (Exception e) { + log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); + } + return versionValid; + } + + private void executeFunction(Connection conn, String query) { + log.info("{} ... ", query); + try { + CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); + callableStatement.execute(); + callableStatement.close(); + log.info("Successfully executed: {}", query.replace(CALL_REGEX, "")); + } catch (Exception e) { + log.info("Failed to execute {} due to: {}", query, e.getMessage()); + } + } + + private void executeQuery(Connection conn, String query) { + try { + conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + Thread.sleep(5000); + } catch (InterruptedException | SQLException e) { + log.info("Failed to drop table {} due to: {}", query.replace("DROP TABLE ", ""), e.getMessage()); + } + } +} \ No newline at end of file diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 8b934cd48f..7015b949b5 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -208,10 +208,6 @@ sql: 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}" # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlTsAnyDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlTsAnyDao.java new file mode 100644 index 0000000000..9a43c530a2 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/SqlTsAnyDao.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2020 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.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +@ConditionalOnExpression("'${database.ts.type}'=='sql' || '${database.ts.type}'=='timescale'") +public @interface SqlTsAnyDao { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java similarity index 100% rename from dao/src/main/java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/util/TimescaleDBTsDao.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java index 833c3745b9..cbe8571922 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java @@ -27,8 +27,8 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Configuration @EnableAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest"}) -@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest"}) -@EntityScan({"org.thingsboard.server.dao.model.sqlts.hsql", "org.thingsboard.server.dao.model.sqlts.latest"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.hsql", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement @SqlTsDao @HsqlDao diff --git a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java index 0e1ad4efb4..796f98d238 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java @@ -22,7 +22,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.SqlDao; -import org.thingsboard.server.dao.util.TimescaleDBTsDao; /** * @author Valerii Sosliuk diff --git a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java index f2aa68c8db..99cea08d7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @Configuration @@ -30,6 +31,7 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; @EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale", "org.thingsboard.server.dao.model.sqlts.dictionary", "org.thingsboard.server.dao.model.sqlts.latest"}) @EnableTransactionManagement @TimescaleDBTsDao +@PsqlDao public class TimescaleDaoConfig { } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index d1a8f9c462..d7ffc72a34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -16,26 +16,42 @@ 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; import javax.persistence.MappedSuperclass; +import javax.persistence.Transient; + +import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_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 AbstractTsKvEntity { +public abstract class AbstractTsKvEntity implements ToData { protected static final String SUM = "SUM"; protected static final String AVG = "AVG"; protected static final String MIN = "MIN"; protected static final String MAX = "MAX"; + @Id + @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") + protected UUID entityId; + @Id @Column(name = TS_COLUMN) protected Long ts; @@ -52,6 +68,9 @@ public abstract class AbstractTsKvEntity { @Column(name = DOUBLE_VALUE_COLUMN) protected Double doubleValue; + @Transient + protected String strKey; + public abstract boolean isNotEmpty(); protected static boolean isAllNull(Object... args) { @@ -62,4 +81,20 @@ public abstract class AbstractTsKvEntity { } return true; } + + @Override + public TsKvEntry toData() { + KvEntry kvEntry = null; + if (strValue != null) { + kvEntry = new StringDataEntry(strKey, strValue); + } else if (longValue != null) { + kvEntry = new LongDataEntry(strKey, longValue); + } else if (doubleValue != null) { + kvEntry = new DoubleDataEntry(strKey, doubleValue); + } else if (booleanValue != null) { + kvEntry = new BooleanDataEntry(strKey, booleanValue); + } + return new BasicTsKvEntry(ts, kvEntry); + } + } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java index c324051a8d..68c6704dcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/dictionary/TsKvDictionary.java @@ -38,7 +38,7 @@ public final class TsKvDictionary { @Column(name = KEY_COLUMN) private String key; - @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="serial") + @Column(name = KEY_ID_COLUMN, unique = true, columnDefinition="int") @Generated(GenerationTime.INSERT) private int keyId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java index 0d1b7f57a4..a17d1373b0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import javax.persistence.Transient; import java.io.Serializable; +import java.util.UUID; @Data @AllArgsConstructor @@ -31,9 +32,8 @@ public class TsKvCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -4089175869616037523L; - private EntityType entityType; - private String entityId; - private String key; + private UUID entityId; + private int key; private long ts; } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java index 97e68655ad..ba24543090 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java @@ -34,6 +34,9 @@ import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; +import javax.persistence.Transient; + +import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; @@ -45,18 +48,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; @IdClass(TsKvCompositeKey.class) public final class TsKvEntity extends AbstractTsKvEntity implements ToData { - @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; + private int key; public TsKvEntity() { } @@ -120,19 +114,4 @@ public final class TsKvEntity extends AbstractTsKvEntity implements ToData { +@SqlResultSetMappings({ + @SqlResultSetMapping( + name = "tsKvLatestFindMapping", + classes = { + @ConstructorResult( + targetClass = TsKvLatestEntity.class, + columns = { + @ColumnResult(name = "entityId", type = UUID.class), + @ColumnResult(name = "key", type = Integer.class), + @ColumnResult(name = "strKey", type = String.class), + @ColumnResult(name = "strValue", type = String.class), + @ColumnResult(name = "boolValue", type = Boolean.class), + @ColumnResult(name = "longValue", type = Long.class), + @ColumnResult(name = "doubleValue", type = Double.class), + @ColumnResult(name = "ts", type = Long.class), - @Id - @Enumerated(EnumType.STRING) - @Column(name = ENTITY_TYPE_COLUMN) - private EntityType entityType; - - @Id - @Column(name = ENTITY_ID_COLUMN) - private String entityId; + } + ), + }) +}) +@NamedNativeQueries({ + @NamedNativeQuery( + name = SearchTsKvLatestRepository.FIND_ALL_BY_ENTITY_ID, + query = SearchTsKvLatestRepository.FIND_ALL_BY_ENTITY_ID_QUERY, + resultSetMapping = "tsKvLatestFindMapping", + resultClass = TsKvLatestEntity.class + ) +}) +public final class TsKvLatestEntity extends AbstractTsKvEntity { @Id @Column(name = KEY_COLUMN) - private String key; + private int key; @Override public boolean isNotEmpty() { return strValue != null || longValue != null || doubleValue != null || booleanValue != null; } - @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); + public TsKvLatestEntity() { } + public TsKvLatestEntity(UUID entityId, Integer key, String strKey, String strValue, Boolean boolValue, Long longValue, Double doubleValue, Long ts) { + this.entityId = entityId; + this.key = key; + this.ts = ts; + this.longValue = longValue; + this.doubleValue = doubleValue; + this.strValue = strValue; + this.booleanValue = boolValue; + this.strKey = strKey; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java index 466bbd673c..d55638d386 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java @@ -41,18 +41,11 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; @Entity @Table(name = "ts_kv") @IdClass(TsKvCompositeKey.class) -public final class TsKvEntity extends AbstractTsKvEntity implements ToData { - - @Id - @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") - protected UUID entityId; +public final class TsKvEntity extends AbstractTsKvEntity { @Id @Column(name = KEY_COLUMN) - protected int key; - - @Transient - protected String strKey; + private int key; public TsKvEntity() { } @@ -116,20 +109,4 @@ public final class TsKvEntity extends AbstractTsKvEntity implements ToData extends AbstractSqlTimeseriesDao { +public abstract class AbstractPsqlHsqlTimeseriesDao extends AbstractSqlTimeseriesDao { @Autowired - private InsertTsRepository insertRepository; - - @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; + protected InsertTsRepository insertRepository; protected TbSqlBlockingQueue> tsQueue; @@ -76,26 +63,39 @@ public abstract class AbstractSimpleSqlTimeseriesDao> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(entityId, query); - } else { - long stepTs = query.getStartTs(); - List>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + query.getInterval(); - long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); - stepTs = endTs; - } - return getTskvEntriesFuture(Futures.allAsList(futures)); + protected abstract ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); + + protected void switchAgregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + switch (aggregation) { + case AVG: + findAvg(tenantId, entityId, key, startTs, endTs, entitiesFutures); + break; + case MAX: + findMax(tenantId, entityId, key, startTs, endTs, entitiesFutures); + break; + case MIN: + findMin(tenantId, entityId, key, startTs, endTs, entitiesFutures); + break; + case SUM: + findSum(tenantId, entityId, key, startTs, endTs, entitiesFutures); + break; + case COUNT: + findCount(tenantId, entityId, key, startTs, endTs, entitiesFutures); + break; + default: + throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - protected abstract ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); + protected abstract void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + + protected abstract void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); - protected abstract ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query); + protected abstract void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); protected SettableFuture setFutures(List> entitiesFutures) { SettableFuture listenableFuture = SettableFuture.create(); @@ -121,36 +121,4 @@ public abstract class AbstractSimpleSqlTimeseriesDao> entitiesFutures) { - switch (aggregation) { - case AVG: - findAvg(entityId, key, startTs, endTs, entitiesFutures); - break; - case MAX: - findMax(entityId, key, startTs, endTs, entitiesFutures); - break; - case MIN: - findMin(entityId, key, startTs, endTs, entitiesFutures); - break; - case SUM: - findSum(entityId, key, startTs, endTs, entitiesFutures); - break; - case COUNT: - findCount(entityId, key, startTs, endTs, entitiesFutures); - break; - default: - throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); - } - } - - protected abstract void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); - - protected abstract void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); - - protected abstract void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); - - protected abstract void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); - - protected abstract void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); -} \ No newline at end of file +} 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 7f340cc48f..58b222ccde 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 @@ -21,9 +21,9 @@ import com.google.common.util.concurrent.FutureCallback; 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.beans.factory.annotation.Value; -import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; @@ -34,12 +34,17 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; +import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; 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.sqlts.dictionary.TsKvDictionaryRepository; +import org.thingsboard.server.dao.sqlts.latest.SearchTsKvLatestRepository; import org.thingsboard.server.dao.sqlts.latest.TsKvLatestRepository; import org.thingsboard.server.dao.timeseries.SimpleListenableFuture; @@ -49,24 +54,34 @@ import javax.annotation.PreDestroy; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; - @Slf4j public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningExecutorService { private static final String DESC_ORDER = "DESC"; + private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); + + private static final ReentrantLock tsCreationLock = new ReentrantLock(); + @Autowired private TsKvLatestRepository tsKvLatestRepository; + @Autowired + private SearchTsKvLatestRepository searchTsKvLatestRepository; + @Autowired private InsertLatestRepository insertLatestRepository; @Autowired - protected ScheduledLogExecutorComponent logExecutor; + private TsKvDictionaryRepository dictionaryRepository; + + private TbSqlBlockingQueue tsLatestQueue; @Value("${sql.ts_latest.batch_size:1000}") private int tsLatestBatchSize; @@ -77,7 +92,17 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx @Value("${sql.ts_latest.stats_print_interval_ms:1000}") private long tsLatestStatsPrintIntervalMs; - private TbSqlBlockingQueue tsLatestQueue; + @Autowired + protected ScheduledLogExecutorComponent logExecutor; + + @Value("${sql.ts.batch_size:1000}") + protected int tsBatchSize; + + @Value("${sql.ts.batch_max_delay:100}") + protected long tsMaxDelay; + + @Value("${sql.ts.stats_print_interval_ms:1000}") + protected long tsStatsPrintIntervalMs; @PostConstruct protected void init() { @@ -120,6 +145,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx protected abstract ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); + protected abstract ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); + protected ListenableFuture> getTskvEntriesFuture(ListenableFuture>> future) { return Futures.transform(future, new Function>, List>() { @Nullable @@ -147,13 +174,14 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx protected ListenableFuture getFindLatestFuture(EntityId entityId, String key) { TsKvLatestCompositeKey compositeKey = new TsKvLatestCompositeKey( - entityId.getEntityType(), - fromTimeUUID(entityId.getId()), - key); + entityId.getId(), + getOrSaveKeyId(key)); Optional entry = tsKvLatestRepository.findById(compositeKey); TsKvEntry result; if (entry.isPresent()) { - result = DaoUtil.getData(entry.get()); + TsKvLatestEntity tsKvLatestEntity = entry.get(); + tsKvLatestEntity.setStrKey(key); + result = DaoUtil.getData(tsKvLatestEntity); } else { result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); } @@ -171,9 +199,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx ListenableFuture removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { if (isRemove) { TsKvLatestEntity latestEntity = new TsKvLatestEntity(); - latestEntity.setEntityType(entityId.getEntityType()); - latestEntity.setEntityId(fromTimeUUID(entityId.getId())); - latestEntity.setKey(query.getKey()); + latestEntity.setEntityId(entityId.getId()); + latestEntity.setKey(getOrSaveKeyId(query.getKey())); return service.submit(() -> { tsKvLatestRepository.delete(latestEntity); return null; @@ -215,17 +242,14 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx protected ListenableFuture> getFindAllLatestFuture(EntityId entityId) { return Futures.immediateFuture( DaoUtil.convertDataList(Lists.newArrayList( - tsKvLatestRepository.findAllByEntityTypeAndEntityId( - entityId.getEntityType(), - UUIDConverter.fromTimeUUID(entityId.getId()))))); + searchTsKvLatestRepository.findAllByEntityId(entityId.getId())))); } protected ListenableFuture getSaveLatestFuture(EntityId entityId, TsKvEntry tsKvEntry) { TsKvLatestEntity latestEntity = new TsKvLatestEntity(); - latestEntity.setEntityType(entityId.getEntityType()); - latestEntity.setEntityId(fromTimeUUID(entityId.getId())); + latestEntity.setEntityId(entityId.getId()); latestEntity.setTs(tsKvEntry.getTs()); - latestEntity.setKey(tsKvEntry.getKey()); + latestEntity.setKey(getOrSaveKeyId(tsKvEntry.getKey())); latestEntity.setStrValue(tsKvEntry.getStrValue().orElse(null)); latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); @@ -233,6 +257,42 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx return tsLatestQueue.add(latestEntity); } + protected Integer getOrSaveKeyId(String strKey) { + Integer keyId = tsKvDictionaryMap.get(strKey); + if (keyId == null) { + Optional tsKvDictionaryOptional; + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + tsCreationLock.lock(); + try { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + if (!tsKvDictionaryOptional.isPresent()) { + TsKvDictionary tsKvDictionary = new TsKvDictionary(); + tsKvDictionary.setKey(strKey); + try { + TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); + tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); + keyId = saved.getKeyId(); + } catch (ConstraintViolationException e) { + tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); + TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); + tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); + keyId = dictionary.getKeyId(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + } + } finally { + tsCreationLock.unlock(); + } + } else { + keyId = tsKvDictionaryOptional.get().getKeyId(); + tsKvDictionaryMap.put(strKey, keyId); + } + } + return keyId; + } + private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); return Futures.transformAsync(future, entryList -> { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java index 60284c73cf..55d2d031d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java @@ -18,11 +18,11 @@ package org.thingsboard.server.dao.sqlts.dictionary; import org.springframework.data.repository.CrudRepository; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; -import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlTsAnyDao; import java.util.Optional; -@PsqlDao +@SqlTsAnyDao public interface TsKvDictionaryRepository extends CrudRepository { Optional findByKeyId(int keyId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java index 5cc344aa2a..91f431ec5e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java @@ -37,15 +37,14 @@ import java.util.List; public class HsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository { 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 " + + "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (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);"; + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; @Override public void saveOrUpdate(List> entities) { @@ -54,29 +53,28 @@ public class HsqlTimeseriesInsertRepository extends AbstractInsertRepository imp public void setValues(PreparedStatement ps, int i) throws SQLException { EntityContainer tsKvEntityEntityContainer = entities.get(i); TsKvEntity tsKvEntity = tsKvEntityEntityContainer.getEntity(); - ps.setString(1, tsKvEntity.getEntityType().name()); - ps.setString(2, tsKvEntity.getEntityId()); - ps.setString(3, tsKvEntity.getKey()); - ps.setLong(4, tsKvEntity.getTs()); + ps.setObject(1, tsKvEntity.getEntityId()); + ps.setInt(2, tsKvEntity.getKey()); + ps.setLong(3, tsKvEntity.getTs()); if (tsKvEntity.getBooleanValue() != null) { - ps.setBoolean(5, tsKvEntity.getBooleanValue()); + ps.setBoolean(4, tsKvEntity.getBooleanValue()); } else { - ps.setNull(5, Types.BOOLEAN); + ps.setNull(4, Types.BOOLEAN); } - ps.setString(6, tsKvEntity.getStrValue()); + ps.setString(5, tsKvEntity.getStrValue()); if (tsKvEntity.getLongValue() != null) { - ps.setLong(7, tsKvEntity.getLongValue()); + ps.setLong(6, tsKvEntity.getLongValue()); } else { - ps.setNull(7, Types.BIGINT); + ps.setNull(6, Types.BIGINT); } if (tsKvEntity.getDoubleValue() != null) { - ps.setDouble(8, tsKvEntity.getDoubleValue()); + ps.setDouble(7, tsKvEntity.getDoubleValue()); } else { - ps.setNull(8, Types.DOUBLE); + ps.setNull(7, Types.DOUBLE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java index c8bafe81e0..3a735acb5f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.AbstractPsqlHsqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.EntityContainer; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.HsqlDao; @@ -41,14 +41,12 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; - @Component @Slf4j @SqlTsDao @HsqlDao -public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao implements TimeseriesDao { +public class JpaHsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao implements TimeseriesDao { @Autowired private TsKvHsqlRepository tsKvRepository; @@ -60,11 +58,12 @@ public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + String strKey = tsKvEntry.getKey(); + Integer keyId = getOrSaveKeyId(strKey); TsKvEntity entity = new TsKvEntity(); - entity.setEntityType(entityId.getEntityType()); - entity.setEntityId(fromTimeUUID(entityId.getId())); + entity.setEntityId(entityId.getId()); entity.setTs(tsKvEntry.getTs()); - entity.setKey(tsKvEntry.getKey()); + entity.setKey(keyId); entity.setStrValue(tsKvEntry.getStrValue().orElse(null)); entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); @@ -77,9 +76,8 @@ public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { return service.submit(() -> { tsKvRepository.delete( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - query.getKey(), + entityId.getId(), + getOrSaveKeyId(query.getKey()), query.getStartTs(), query.getEndTs()); return null; @@ -116,14 +114,47 @@ public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + if (query.getAggregation() == Aggregation.NONE) { + return findAllAsyncWithLimit(tenantId, entityId, query); + } else { + long stepTs = query.getStartTs(); + List>> futures = new ArrayList<>(); + while (stepTs < query.getEndTs()) { + long startTs = stepTs; + long endTs = stepTs + query.getInterval(); + long ts = startTs + (endTs - startTs) / 2; + futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); + stepTs = endTs; + } + return getTskvEntriesFuture(Futures.allAsList(futures)); + } + } + + @Override + protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + List tsKvEntities = tsKvRepository.findAllWithLimit( + entityId.getId(), + getOrSaveKeyId(query.getKey()), + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), "ts"))); + tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); + return Futures.immediateFuture( + DaoUtil.convertDataList( + tsKvEntities)); + } + + @Override + protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { List> entitiesFutures = new ArrayList<>(); - switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); + switchAgregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); return Futures.transform(setFutures(entitiesFutures), entity -> { if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(fromTimeUUID(entityId.getId())); - entity.setEntityType(entityId.getEntityType()); - entity.setKey(key); + entity.setEntityId(entityId.getId()); + entity.setKey(getOrSaveKeyId(key)); entity.setTs(ts); return Optional.of(DaoUtil.getData(entity)); } else { @@ -132,75 +163,63 @@ public class JpaHsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { - return Futures.immediateFuture( - DaoUtil.convertDataList( - tsKvRepository.findAllWithLimit( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - query.getKey(), - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), "ts"))))); - } - - protected void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + @Override + protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findCount( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); } - protected void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + @Override + protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findSum( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); } - protected void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + @Override + protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMin( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); entitiesFutures.add(tsKvRepository.findNumericMin( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); } - protected void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + @Override + protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMax( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); entitiesFutures.add(tsKvRepository.findNumericMax( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); } - protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + @Override + protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findAvg( - fromTimeUUID(entityId.getId()), - entityId.getEntityType(), - key, + entityId.getId(), + keyId, startTs, endTs)); } - } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java index f770b4dca9..552d515e44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java @@ -22,23 +22,21 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.scheduling.annotation.Async; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sqlts.hsql.TsKvCompositeKey; import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; +import java.util.UUID; import java.util.concurrent.CompletableFuture; @SqlDao public interface TsKvHsqlRepository extends CrudRepository { @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.entityType = :entityType AND tskv.key = :entityKey " + - "AND tskv.ts > :startTs AND tskv.ts <= :endTs") - List findAllWithLimit(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String key, + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + List findAllWithLimit(@Param("entityId") UUID entityId, + @Param("entityKey") int key, @Param("startTs") long startTs, @Param("endTs") long endTs, Pageable pageable); @@ -46,22 +44,18 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - void delete(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String key, + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + void delete(@Param("entityId") UUID entityId, + @Param("entityKey") int key, @Param("startTs") long startTs, @Param("endTs") long endTs); @Async @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + - "WHERE tskv.strValue IS NOT NULL " + - "AND tskv.entityId = :entityId AND tskv.entityType = :entityType " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findStringMax(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + "WHERE tskv.strValue IS NOT NULL AND tskv.entityId = :entityId AND tskv.key = :entityKey" + + " AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findStringMax(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @@ -70,24 +64,20 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - CompletableFuture findNumericMax(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + CompletableFuture findNumericMax(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @Async @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + - "WHERE tskv.strValue IS NOT NULL " + - "AND tskv.entityId = :entityId AND tskv.entityType = :entityType " + + "WHERE tskv.strValue IS NOT NULL AND tskv.entityId = :entityId " + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findStringMin(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + CompletableFuture findStringMin(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @@ -96,12 +86,10 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - CompletableFuture findNumericMin(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + CompletableFuture findNumericMin(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @@ -110,11 +98,9 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - CompletableFuture findCount(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") + CompletableFuture findCount(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @@ -123,12 +109,10 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - CompletableFuture findAvg(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + CompletableFuture findAvg(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); @@ -137,12 +121,10 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - CompletableFuture findSum(@Param("entityId") String entityId, - @Param("entityType") EntityType entityType, - @Param("entityKey") String entityKey, + CompletableFuture findSum(@Param("entityId") UUID entityId, + @Param("entityKey") int entityKey, @Param("startTs") long startTs, @Param("endTs") long endTs); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java index 5371a77e64..9a50cd15c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java @@ -36,43 +36,41 @@ import java.util.List; public class HsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { 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 " + + "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "ON (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);"; + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; @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()); + ps.setObject(1, entities.get(i).getEntityId()); + ps.setInt(2, entities.get(i).getKey()); + ps.setLong(3, entities.get(i).getTs()); if (entities.get(i).getBooleanValue() != null) { - ps.setBoolean(5, entities.get(i).getBooleanValue()); + ps.setBoolean(4, entities.get(i).getBooleanValue()); } else { - ps.setNull(5, Types.BOOLEAN); + ps.setNull(4, Types.BOOLEAN); } - ps.setString(6, entities.get(i).getStrValue()); + ps.setString(5, entities.get(i).getStrValue()); if (entities.get(i).getLongValue() != null) { - ps.setLong(7, entities.get(i).getLongValue()); + ps.setLong(6, entities.get(i).getLongValue()); } else { - ps.setNull(7, Types.BIGINT); + ps.setNull(6, Types.BIGINT); } if (entities.get(i).getDoubleValue() != null) { - ps.setDouble(8, entities.get(i).getDoubleValue()); + ps.setDouble(7, entities.get(i).getDoubleValue()); } else { - ps.setNull(8, Types.DOUBLE); + ps.setNull(7, Types.DOUBLE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java index bace8ff637..95c88926cf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java @@ -38,12 +38,12 @@ import java.util.List; public class PsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { 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 = ?"; + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE 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 = ?;"; + "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; @Override public void saveOrUpdate(List entities) { @@ -76,9 +76,8 @@ public class PsqlLatestInsertRepository extends AbstractInsertRepository impleme ps.setNull(5, Types.DOUBLE); } - ps.setString(6, tsKvLatestEntity.getEntityType().name()); - ps.setString(7, tsKvLatestEntity.getEntityId()); - ps.setString(8, tsKvLatestEntity.getKey()); + ps.setObject(6, tsKvLatestEntity.getEntityId()); + ps.setInt(7, tsKvLatestEntity.getKey()); } @Override @@ -105,38 +104,37 @@ public class PsqlLatestInsertRepository extends AbstractInsertRepository impleme @Override public void setValues(PreparedStatement ps, int i) throws SQLException { TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i); - ps.setString(1, tsKvLatestEntity.getEntityType().name()); - ps.setString(2, tsKvLatestEntity.getEntityId()); - ps.setString(3, tsKvLatestEntity.getKey()); - ps.setLong(4, tsKvLatestEntity.getTs()); - ps.setLong(9, tsKvLatestEntity.getTs()); + ps.setObject(1, tsKvLatestEntity.getEntityId()); + ps.setInt(2, tsKvLatestEntity.getKey()); + ps.setLong(3, tsKvLatestEntity.getTs()); + ps.setLong(8, tsKvLatestEntity.getTs()); if (tsKvLatestEntity.getBooleanValue() != null) { - ps.setBoolean(5, tsKvLatestEntity.getBooleanValue()); - ps.setBoolean(10, tsKvLatestEntity.getBooleanValue()); + ps.setBoolean(4, tsKvLatestEntity.getBooleanValue()); + ps.setBoolean(9, tsKvLatestEntity.getBooleanValue()); } else { - ps.setNull(5, Types.BOOLEAN); - ps.setNull(10, Types.BOOLEAN); + ps.setNull(4, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); } - ps.setString(6, replaceNullChars(tsKvLatestEntity.getStrValue())); - ps.setString(11, replaceNullChars(tsKvLatestEntity.getStrValue())); + ps.setString(5, replaceNullChars(tsKvLatestEntity.getStrValue())); + ps.setString(10, replaceNullChars(tsKvLatestEntity.getStrValue())); if (tsKvLatestEntity.getLongValue() != null) { - ps.setLong(7, tsKvLatestEntity.getLongValue()); - ps.setLong(12, tsKvLatestEntity.getLongValue()); + ps.setLong(6, tsKvLatestEntity.getLongValue()); + ps.setLong(11, tsKvLatestEntity.getLongValue()); } else { - ps.setNull(7, Types.BIGINT); - ps.setNull(12, Types.BIGINT); + ps.setNull(6, Types.BIGINT); + ps.setNull(11, Types.BIGINT); } if (tsKvLatestEntity.getDoubleValue() != null) { - ps.setDouble(8, tsKvLatestEntity.getDoubleValue()); - ps.setDouble(13, tsKvLatestEntity.getDoubleValue()); + ps.setDouble(7, tsKvLatestEntity.getDoubleValue()); + ps.setDouble(12, tsKvLatestEntity.getDoubleValue()); } else { - ps.setNull(8, Types.DOUBLE); - ps.setNull(13, Types.DOUBLE); + ps.setNull(7, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java new file mode 100644 index 0000000000..5940a33d31 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 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.latest; + +import org.springframework.stereotype.Repository; +import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; +import org.thingsboard.server.dao.util.SqlTsAnyDao; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import java.util.List; +import java.util.UUID; + +@SqlTsAnyDao +@Repository +public class SearchTsKvLatestRepository { + + public static final String FIND_ALL_BY_ENTITY_ID = "findAllByEntityId"; + public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, ts_kv_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," + + " ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " + + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)"; + + @PersistenceContext + private EntityManager entityManager; + + public List findAllByEntityId(UUID entityId) { + return entityManager.createNamedQuery(FIND_ALL_BY_ENTITY_ID, TsKvLatestEntity.class) + .setParameter("id", entityId) + .getResultList(); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index 71c8a00057..9ba59c10ef 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -16,15 +16,15 @@ package org.thingsboard.server.dao.sqlts.latest; import org.springframework.data.repository.CrudRepository; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.util.SqlDao; import java.util.List; +import java.util.UUID; @SqlDao public interface TsKvLatestRepository extends CrudRepository { - List findAllByEntityTypeAndEntityId(EntityType entityType, String entityId); + List findAllByEntityId(UUID entityId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index ccf2490fdc..bcbcc2762d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.sqlts.psql; 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.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; @@ -31,12 +30,9 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractSimpleSqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.AbstractPsqlHsqlTimeseriesDao; import org.thingsboard.server.dao.sqlts.EntityContainer; -import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; import org.thingsboard.server.dao.timeseries.PsqlPartition; import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.timeseries.TimeseriesDao; @@ -48,10 +44,12 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantLock; import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_START; @@ -61,17 +59,11 @@ import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_STA @Slf4j @SqlTsDao @PsqlDao -public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao implements TimeseriesDao { +public class JpaPsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao implements TimeseriesDao { - private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); private final Map partitions = new ConcurrentHashMap<>(); - - private static final ReentrantLock tsCreationLock = new ReentrantLock(); private static final ReentrantLock partitionCreationLock = new ReentrantLock(); - @Autowired - private TsKvDictionaryRepository dictionaryRepository; - @Autowired private TsKvPsqlRepository tsKvRepository; @@ -100,11 +92,6 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { - return processFindAllAsync(tenantId, entityId, queries); - } - @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { String strKey = tsKvEntry.getKey(); @@ -166,22 +153,25 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - List> entitiesFutures = new ArrayList<>(); - switchAgregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); - return Futures.transform(setFutures(entitiesFutures), entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityId.getId()); - entity.setStrKey(key); - entity.setTs(ts); - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); + protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + if (query.getAggregation() == Aggregation.NONE) { + return findAllAsyncWithLimit(tenantId, entityId, query); + } else { + long stepTs = query.getStartTs(); + List>> futures = new ArrayList<>(); + while (stepTs < query.getEndTs()) { + long startTs = stepTs; + long endTs = stepTs + query.getInterval(); + long ts = startTs + (endTs - startTs) / 2; + futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); + stepTs = endTs; } - }); + return getTskvEntriesFuture(Futures.allAsList(futures)); + } } - protected ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { + @Override + protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { Integer keyId = getOrSaveKeyId(query.getKey()); List tsKvEntities = tsKvRepository.findAllWithLimit( entityId.getId(), @@ -195,7 +185,24 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> entitiesFutures) { + @Override + protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + List> entitiesFutures = new ArrayList<>(); + switchAgregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); + return Futures.transform(setFutures(entitiesFutures), entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(entityId.getId()); + entity.setStrKey(key); + entity.setTs(ts); + return Optional.of(DaoUtil.getData(entity)); + } else { + return Optional.empty(); + } + }); + } + + @Override + protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findCount( entityId.getId(), @@ -204,7 +211,8 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> entitiesFutures) { + @Override + protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findSum( entityId.getId(), @@ -213,7 +221,8 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> entitiesFutures) { + @Override + protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMin( entityId.getId(), @@ -227,7 +236,8 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> entitiesFutures) { + @Override + protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMax( entityId.getId(), @@ -241,7 +251,8 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao> entitiesFutures) { + @Override + protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findAvg( entityId.getId(), @@ -250,40 +261,9 @@ public class JpaPsqlTimeseriesDao extends AbstractSimpleSqlTimeseriesDao tsKvDictionaryOptional; - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { - tsCreationLock.lock(); - try { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { - TsKvDictionary tsKvDictionary = new TsKvDictionary(); - tsKvDictionary.setKey(strKey); - try { - TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); - tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); - keyId = saved.getKeyId(); - } catch (ConstraintViolationException e) { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); - tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); - keyId = dictionary.getKeyId(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - } - } finally { - tsCreationLock.unlock(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - tsKvDictionaryMap.put(strKey, keyId); - } - } - return keyId; + @Override + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + return processFindAllAsync(tenantId, entityId, queries); } private void savePartition(PsqlPartition psqlPartition) { 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 6950e0b680..a57e328bc2 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 @@ -19,9 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; 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.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -33,15 +31,12 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; -import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; 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.EntityContainer; import org.thingsboard.server.dao.sqlts.InsertTsRepository; -import org.thingsboard.server.dao.sqlts.dictionary.TsKvDictionaryRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @@ -53,25 +48,12 @@ import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantLock; - @Component @Slf4j @TimescaleDBTsDao public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements TimeseriesDao { - private static final String TS = "ts"; - - private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); - - private static final ReentrantLock tsCreationLock = new ReentrantLock(); - - @Autowired - private TsKvDictionaryRepository dictionaryRepository; - @Autowired private TsKvTimescaleRepository tsKvRepository; @@ -79,40 +61,32 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements private AggregationRepository aggregationRepository; @Autowired - private InsertTsRepository insertRepository; - - @Value("${sql.ts_timescale.batch_size:1000}") - private int batchSize; - - @Value("${sql.ts_timescale.batch_max_delay:100}") - private long maxDelay; + protected InsertTsRepository insertRepository; - @Value("${sql.ts_timescale.stats_print_interval_ms:1000}") - private long statsPrintIntervalMs; - - private TbSqlBlockingQueue> queue; + protected TbSqlBlockingQueue> tsQueue; @PostConstruct protected void init() { super.init(); - TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + TbSqlBlockingQueueParams tsParams = TbSqlBlockingQueueParams.builder() .logName("TS Timescale") - .batchSize(batchSize) - .maxDelay(maxDelay) - .statsPrintIntervalMs(statsPrintIntervalMs) + .batchSize(tsBatchSize) + .maxDelay(tsMaxDelay) + .statsPrintIntervalMs(tsStatsPrintIntervalMs) .build(); - queue = new TbSqlBlockingQueue<>(params); - queue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); + tsQueue = new TbSqlBlockingQueue<>(tsParams); + tsQueue.init(logExecutor, v -> insertRepository.saveOrUpdate(v)); } @PreDestroy protected void destroy() { super.destroy(); - if (queue != null) { - queue.destroy(); + if (tsQueue != null) { + tsQueue.destroy(); } } + @Override protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { return findAllAsyncWithLimit(tenantId, entityId, query); @@ -120,11 +94,58 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements long startTs = query.getStartTs(); long endTs = query.getEndTs(); long timeBucket = query.getInterval(); - ListenableFuture>> future = findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); + ListenableFuture>> future = findAllAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); return getTskvEntriesFuture(future); } } + @Override + protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + String strKey = query.getKey(); + Integer keyId = getOrSaveKeyId(strKey); + List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( + tenantId.getId(), + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), "ts"))); + timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey)); + return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities)); + } + + private ListenableFuture>> findAllAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { + CompletableFuture> listCompletableFuture = switchAgregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); + SettableFuture> listenableFuture = SettableFuture.create(); + listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { + if (throwable != null) { + listenableFuture.setException(throwable); + } else { + listenableFuture.set(timescaleTsKvEntities); + } + }); + return Futures.transform(listenableFuture, timescaleTsKvEntities -> { + if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { + List> result = new ArrayList<>(); + timescaleTsKvEntities.forEach(entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(entityId.getId()); + entity.setTenantId(tenantId.getId()); + entity.setStrKey(key); + result.add(Optional.of(DaoUtil.getData(entity))); + } else { + result.add(Optional.empty()); + } + }); + return result; + } else { + return Collections.emptyList(); + } + }); + } + @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -154,7 +175,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); log.trace("Saving entity to timescale db: {}", entity); - return queue.add(new EntityContainer(entity, null)); + return tsQueue.add(new EntityContainer(entity, null)); } @Override @@ -192,88 +213,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements return service.submit(() -> null); } - private ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - String strKey = query.getKey(); - Integer keyId = getOrSaveKeyId(strKey); - List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( - tenantId.getId(), - entityId.getId(), - keyId, - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), TS))); - timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey)); - return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities)); - } - - private Integer getOrSaveKeyId(String strKey) { - Integer keyId = tsKvDictionaryMap.get(strKey); - if (keyId == null) { - Optional tsKvDictionaryOptional; - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { - tsCreationLock.lock(); - try { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - if (!tsKvDictionaryOptional.isPresent()) { - TsKvDictionary tsKvDictionary = new TsKvDictionary(); - tsKvDictionary.setKey(strKey); - try { - TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); - tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); - keyId = saved.getKeyId(); - } catch (ConstraintViolationException e) { - tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); - TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); - tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); - keyId = dictionary.getKeyId(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - } - } finally { - tsCreationLock.unlock(); - } - } else { - keyId = tsKvDictionaryOptional.get().getKeyId(); - tsKvDictionaryMap.put(strKey, keyId); - } - } - return keyId; - } - - private ListenableFuture>> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { - CompletableFuture> listCompletableFuture = switchAgregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); - SettableFuture> listenableFuture = SettableFuture.create(); - listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { - if (throwable != null) { - listenableFuture.setException(throwable); - } else { - listenableFuture.set(timescaleTsKvEntities); - } - }); - return Futures.transform(listenableFuture, timescaleTsKvEntities -> { - if (!CollectionUtils.isEmpty(timescaleTsKvEntities)) { - List> result = new ArrayList<>(); - timescaleTsKvEntities.forEach(entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityId.getId()); - entity.setTenantId(tenantId.getId()); - entity.setStrKey(key); - result.add(Optional.of(DaoUtil.getData(entity))); - } else { - result.add(Optional.empty()); - } - }); - return result; - } else { - return Collections.emptyList(); - } - }); - } - private CompletableFuture> switchAgregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) { switch (aggregation) { case AVG: @@ -291,9 +230,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } } - private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findAvg( + return aggregationRepository.findCount( tenantId, entityId, keyId, @@ -302,9 +241,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findMax( + return aggregationRepository.findSum( tenantId, entityId, keyId, @@ -322,12 +261,11 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements timeBucket, startTs, endTs); - } - private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findSum( + return aggregationRepository.findMax( tenantId, entityId, keyId, @@ -336,9 +274,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { Integer keyId = getOrSaveKeyId(key); - return aggregationRepository.findCount( + return aggregationRepository.findAvg( tenantId, entityId, keyId, diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index bcdc436608..4cec6ec13b 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv ( str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) + CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) ); CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( @@ -35,15 +35,14 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( ); CREATE TABLE IF NOT EXISTS ts_kv_latest ( - entity_type varchar(255) NOT NULL, - entity_id varchar(31) NOT NULL, - key varchar(255) NOT NULL, + entity_id uuid NOT NULL, + key int NOT NULL, ts bigint NOT NULL, bool_v boolean, str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-ts-hsql.sql b/dao/src/main/resources/sql/schema-ts-hsql.sql index cde23b2872..c29d7e2ed7 100644 --- a/dao/src/main/resources/sql/schema-ts-hsql.sql +++ b/dao/src/main/resources/sql/schema-ts-hsql.sql @@ -14,26 +14,32 @@ -- limitations under the License. -- +SET DATABASE SQL SYNTAX PGS TRUE; + CREATE TABLE IF NOT EXISTS ts_kv ( - entity_type varchar(255) NOT NULL, - entity_id varchar(31) NOT NULL, - key varchar(255) NOT NULL, + entity_id uuid NOT NULL, + key int NOT NULL, ts bigint NOT NULL, bool_v boolean, str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts) + CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) ); CREATE TABLE IF NOT EXISTS ts_kv_latest ( - entity_type varchar(255) NOT NULL, - entity_id varchar(31) NOT NULL, - key varchar(255) NOT NULL, + entity_id uuid NOT NULL, + key int NOT NULL, ts bigint NOT NULL, bool_v boolean, str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) +); + +CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( + key varchar(255) NOT NULL, + key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index bd9e3a693d..465c2d51e3 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -24,20 +24,19 @@ CREATE TABLE IF NOT EXISTS ts_kv ( dbl_v double precision ) PARTITION BY RANGE (ts); -CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( - key varchar(255) NOT NULL, - key_id serial UNIQUE, - CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) -); - CREATE TABLE IF NOT EXISTS ts_kv_latest ( - entity_type varchar(255) NOT NULL, - entity_id varchar(31) NOT NULL, - key varchar(255) NOT NULL, + entity_id uuid NOT NULL, + key int NOT NULL, ts bigint NOT NULL, bool_v boolean, str_v varchar(10000000), long_v bigint, dbl_v double precision, - CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key) + CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) +); + +CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( + key varchar(255) NOT NULL, + key_id serial UNIQUE, + CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); \ No newline at end of file From bdee8951c49ca38c9d893873a98f297c1eca81bf Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 7 Feb 2020 15:38:04 +0200 Subject: [PATCH 188/261] refactored sqlUpgradeService implementation (#2395) * refactored sqlUpgradeService implementation * fix typo * change string constant name * add ability to re-init chunks for upgrade timescale --- .../upgrade/2.4.3/schema_update_psql_ts.sql | 52 +++---- .../2.4.3/schema_update_timescale_ts.sql | 52 +++---- .../AbstractSqlTsDatabaseUpgradeService.java | 124 +++++++++++++++ .../install/PsqlTsDatabaseUpgradeService.java | 127 ++++++--------- .../SqlAbstractDatabaseSchemaService.java | 6 +- .../SqlTimescaleDatabaseSchemaService.java | 31 ---- .../SqlTimescaleDatabaseUpgradeService.java | 147 ------------------ .../TimescaleTsDatabaseSchemaService.java | 68 ++++++++ .../TimescaleTsDatabaseUpgradeService.java | 125 +++++++++++++++ .../src/main/resources/thingsboard.yml | 8 +- ...tractChunkedAggregationTimeseriesDao.java} | 4 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 5 +- ...ory.java => InsertLatestTsRepository.java} | 2 +- ...itory.java => HsqlInsertTsRepository.java} | 2 +- .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 6 +- ...java => HsqlLatestInsertTsRepository.java} | 4 +- ...java => PsqlLatestInsertTsRepository.java} | 4 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 8 +- ...itory.java => PsqlInsertTsRepository.java} | 2 +- ....java => TimescaleInsertTsRepository.java} | 2 +- .../timescale/TimescaleTimeseriesDao.java | 4 +- .../main/resources/sql/schema-timescale.sql | 4 +- 22 files changed, 449 insertions(+), 338 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseSchemaService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{AbstractPsqlHsqlTimeseriesDao.java => AbstractChunkedAggregationTimeseriesDao.java} (94%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{InsertLatestRepository.java => InsertLatestTsRepository.java} (94%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/{HsqlTimeseriesInsertRepository.java => HsqlInsertTsRepository.java} (96%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/{HsqlLatestInsertRepository.java => HsqlLatestInsertTsRepository.java} (94%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/{PsqlLatestInsertRepository.java => PsqlLatestInsertTsRepository.java} (97%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/{PsqlTimeseriesInsertRepository.java => PsqlInsertTsRepository.java} (97%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/{TimescaleInsertRepository.java => TimescaleInsertTsRepository.java} (96%) diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql index add03ed8f7..2d012336ab 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql @@ -160,18 +160,18 @@ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(first, '-', second, '-1', third, '-', fourth, '-', fifth)::uuid AS entity_id, - substrings.key AS key, - substrings.ts AS ts, - substrings.bool_v AS bool_v, - substrings.str_v AS str_v, - substrings.long_v AS long_v, - substrings.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first, - SUBSTRING(entity_id, 4, 4) AS second, - SUBSTRING(entity_id, 1, 3) AS third, - SUBSTRING(entity_id, 16, 4) AS fourth, - SUBSTRING(entity_id, 20) AS fifth, + insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid AS entity_id, + ts_kv_records.key AS key, + ts_kv_records.ts AS ts, + ts_kv_records.bool_v AS bool_v, + ts_kv_records.str_v AS str_v, + ts_kv_records.long_v AS long_v, + ts_kv_records.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first_part_uuid, + SUBSTRING(entity_id, 4, 4) AS second_part_uuid, + SUBSTRING(entity_id, 1, 3) AS third_part_uuid, + SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid, + SUBSTRING(entity_id, 20) AS fifth_part_uuid, key_id AS key, ts, bool_v, @@ -179,7 +179,7 @@ DECLARE long_v, dbl_v FROM ts_kv_old - INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS substrings; + INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records; BEGIN OPEN insert_cursor; LOOP @@ -208,18 +208,18 @@ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(first, '-', second, '-1', third, '-', fourth, '-', fifth)::uuid AS entity_id, - substrings.key AS key, - substrings.ts AS ts, - substrings.bool_v AS bool_v, - substrings.str_v AS str_v, - substrings.long_v AS long_v, - substrings.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first, - SUBSTRING(entity_id, 4, 4) AS second, - SUBSTRING(entity_id, 1, 3) AS third, - SUBSTRING(entity_id, 16, 4) AS fourth, - SUBSTRING(entity_id, 20) AS fifth, + insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid AS entity_id, + ts_kv_latest_records.key AS key, + ts_kv_latest_records.ts AS ts, + ts_kv_latest_records.bool_v AS bool_v, + ts_kv_latest_records.str_v AS str_v, + ts_kv_latest_records.long_v AS long_v, + ts_kv_latest_records.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first_part_uuid, + SUBSTRING(entity_id, 4, 4) AS second_part_uuid, + SUBSTRING(entity_id, 1, 3) AS third_part_uuid, + SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid, + SUBSTRING(entity_id, 20) AS fifth_part_uuid, key_id AS key, ts, bool_v, @@ -227,7 +227,7 @@ DECLARE long_v, dbl_v FROM ts_kv_latest_old - INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS substrings; + INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS ts_kv_latest_records; BEGIN OPEN insert_cursor; LOOP diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql index 715acd96c6..b8a3f1850e 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql @@ -38,9 +38,9 @@ BEGIN END; $$ LANGUAGE 'plpgsql'; --- select create_tenant_ts_kv_table_copy(); +-- select create_new_tenant_ts_kv_table(); -CREATE OR REPLACE FUNCTION create_tenant_ts_kv_table_copy() RETURNS VOID AS $$ +CREATE OR REPLACE FUNCTION create_new_tenant_ts_kv_table() RETURNS VOID AS $$ BEGIN ALTER TABLE tenant_ts_kv @@ -59,7 +59,7 @@ BEGIN ADD CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY(tenant_id, entity_id, key, ts); ALTER INDEX idx_tenant_ts_kv RENAME TO idx_tenant_ts_kv_old; ALTER INDEX tenant_ts_kv_ts_idx RENAME TO tenant_ts_kv_ts_idx_old; - PERFORM create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); +-- PERFORM create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts); END; $$ LANGUAGE 'plpgsql'; @@ -132,24 +132,24 @@ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(tenant_id_first, '-', tenant_id_second, '-1', tenant_id_third, '-', tenant_id_fourth, '-', tenant_id_fifth)::uuid AS tenant_id, - CONCAT(entity_id_first, '-', entity_id_second, '-1', entity_id_third, '-', entity_id_fourth, '-', entity_id_fifth)::uuid AS entity_id, - substrings.key AS key, - substrings.ts AS ts, - substrings.bool_v AS bool_v, - substrings.str_v AS str_v, - substrings.long_v AS long_v, - substrings.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(tenant_id, 8, 8) AS tenant_id_first, - SUBSTRING(tenant_id, 4, 4) AS tenant_id_second, - SUBSTRING(tenant_id, 1, 3) AS tenant_id_third, - SUBSTRING(tenant_id, 16, 4) AS tenant_id_fourth, - SUBSTRING(tenant_id, 20) AS tenant_id_fifth, - SUBSTRING(entity_id, 8, 8) AS entity_id_first, - SUBSTRING(entity_id, 4, 4) AS entity_id_second, - SUBSTRING(entity_id, 1, 3) AS entity_id_third, - SUBSTRING(entity_id, 16, 4) AS entity_id_fourth, - SUBSTRING(entity_id, 20) AS entity_id_fifth, + insert_cursor CURSOR FOR SELECT CONCAT(tenant_id_first_part_uuid, '-', tenant_id_second_part_uuid, '-1', tenant_id_third_part_uuid, '-', tenant_id_fourth_part_uuid, '-', tenant_id_fifth_part_uuid)::uuid AS tenant_id, + CONCAT(entity_id_first_part_uuid, '-', entity_id_second_part_uuid, '-1', entity_id_third_part_uuid, '-', entity_id_fourth_part_uuid, '-', entity_id_fifth_part_uuid)::uuid AS entity_id, + tenant_ts_kv_records.key AS key, + tenant_ts_kv_records.ts AS ts, + tenant_ts_kv_records.bool_v AS bool_v, + tenant_ts_kv_records.str_v AS str_v, + tenant_ts_kv_records.long_v AS long_v, + tenant_ts_kv_records.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(tenant_id, 8, 8) AS tenant_id_first_part_uuid, + SUBSTRING(tenant_id, 4, 4) AS tenant_id_second_part_uuid, + SUBSTRING(tenant_id, 1, 3) AS tenant_id_third_part_uuid, + SUBSTRING(tenant_id, 16, 4) AS tenant_id_fourth_part_uuid, + SUBSTRING(tenant_id, 20) AS tenant_id_fifth_part_uuid, + SUBSTRING(entity_id, 8, 8) AS entity_id_first_part_uuid, + SUBSTRING(entity_id, 4, 4) AS entity_id_second_part_uuid, + SUBSTRING(entity_id, 1, 3) AS entity_id_third_part_uuid, + SUBSTRING(entity_id, 16, 4) AS entity_id_fourth_part_uuid, + SUBSTRING(entity_id, 20) AS entity_id_fifth_part_uuid, key_id AS key, ts, bool_v, @@ -157,7 +157,7 @@ DECLARE long_v, dbl_v FROM tenant_ts_kv_old - INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS substrings; + INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS tenant_ts_kv_records; BEGIN OPEN insert_cursor; LOOP @@ -188,10 +188,10 @@ DECLARE latest_record RECORD; insert_record RECORD; insert_cursor CURSOR FOR SELECT - latest.key AS key, - latest.entity_id AS entity_id, - latest.ts AS ts - FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM tenant_ts_kv GROUP BY key, entity_id) AS latest; + latest_records.key AS key, + latest_records.entity_id AS entity_id, + latest_records.ts AS ts + FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM tenant_ts_kv GROUP BY key, entity_id) AS latest_records; BEGIN OPEN insert_cursor; LOOP diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java new file mode 100644 index 0000000000..01bed834b8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java @@ -0,0 +1,124 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.sql.CallableStatement; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Types; + +@Slf4j +public abstract class AbstractSqlTsDatabaseUpgradeService { + + protected static final String CALL_REGEX = "call "; + protected static final String CHECK_VERSION = "check_version()"; + protected static final String DROP_TABLE = "DROP TABLE "; + protected static final String DROP_FUNCTION_IF_EXISTS = "DROP FUNCTION IF EXISTS "; + + private static final String CALL_CHECK_VERSION = CALL_REGEX + CHECK_VERSION; + + + private static final String FUNCTION = "function: {}"; + private static final String DROP_STATEMENT = "drop statement: {}"; + private static final String QUERY = "query: {}"; + private static final String SUCCESSFULLY_EXECUTED = "Successfully executed "; + private static final String FAILED_TO_EXECUTE = "Failed to execute "; + private static final String FAILED_DUE_TO = " due to: {}"; + + protected static final String SUCCESSFULLY_EXECUTED_FUNCTION = SUCCESSFULLY_EXECUTED + FUNCTION; + protected static final String FAILED_TO_EXECUTE_FUNCTION_DUE_TO = FAILED_TO_EXECUTE + FUNCTION + FAILED_DUE_TO; + + protected static final String SUCCESSFULLY_EXECUTED_DROP_STATEMENT = SUCCESSFULLY_EXECUTED + DROP_STATEMENT; + protected static final String FAILED_TO_EXECUTE_DROP_STATEMENT = FAILED_TO_EXECUTE + DROP_STATEMENT + FAILED_DUE_TO; + + protected static final String SUCCESSFULLY_EXECUTED_QUERY = SUCCESSFULLY_EXECUTED + QUERY; + protected static final String FAILED_TO_EXECUTE_QUERY = FAILED_TO_EXECUTE + QUERY + FAILED_DUE_TO; + + @Value("${spring.datasource.url}") + protected String dbUrl; + + @Value("${spring.datasource.username}") + protected String dbUserName; + + @Value("${spring.datasource.password}") + protected String dbPassword; + + @Autowired + protected InstallScripts installScripts; + + protected abstract void loadSql(Connection conn); + + protected void loadFunctions(Path sqlFile, Connection conn) throws Exception { + String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } + + protected boolean checkVersion(Connection conn) { + log.info("Check the current PostgreSQL version..."); + boolean versionValid = false; + try { + CallableStatement callableStatement = conn.prepareCall("{? = " + CALL_CHECK_VERSION + " }"); + callableStatement.registerOutParameter(1, Types.BOOLEAN); + callableStatement.execute(); + versionValid = callableStatement.getBoolean(1); + callableStatement.close(); + } catch (Exception e) { + log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); + } + return versionValid; + } + + protected void executeFunction(Connection conn, String query) { + log.info("{} ... ", query); + try { + CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); + callableStatement.execute(); + callableStatement.close(); + log.info(SUCCESSFULLY_EXECUTED_FUNCTION, query.replace(CALL_REGEX, "")); + Thread.sleep(2000); + } catch (Exception e) { + log.info(FAILED_TO_EXECUTE_FUNCTION_DUE_TO, query, e.getMessage()); + } + } + + protected void executeDropStatement(Connection conn, String query) { + try { + conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + log.info(SUCCESSFULLY_EXECUTED_DROP_STATEMENT, query); + Thread.sleep(5000); + } catch (InterruptedException | SQLException e) { + log.info(FAILED_TO_EXECUTE_DROP_STATEMENT, query, e.getMessage()); + } + } + + protected void executeQuery(Connection conn, String query) { + try { + conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + log.info(SUCCESSFULLY_EXECUTED_QUERY, query); + Thread.sleep(5000); + } catch (InterruptedException | SQLException e) { + log.info(FAILED_TO_EXECUTE_QUERY, query, e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index 8ce67b1a42..2b1cbd053d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -16,54 +16,55 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.sql.CallableStatement; import java.sql.Connection; import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Types; @Service @Profile("install") @Slf4j @SqlTsDao @PsqlDao -public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { +public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { - private static final String CALL_REGEX = "call "; private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; - private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; - private static final String CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + "create_partition_ts_kv_table()"; - private static final String CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + "create_new_ts_kv_latest_table()"; - private static final String CREATE_PARTITIONS = CALL_REGEX + "create_partitions()"; - private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; - private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; - private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_ts_kv()"; - private static final String INSERT_INTO_TS_KV_LATEST = CALL_REGEX + "insert_into_ts_kv_latest()"; - private static final String DROP_TABLE_TS_KV_OLD = "DROP TABLE ts_kv_old;"; - private static final String DROP_TABLE_TS_KV_LATEST_OLD = "DROP TABLE ts_kv_latest_old;"; - @Value("${spring.datasource.url}") - private String dbUrl; + private static final String TS_KV_OLD = "ts_kv_old;"; + private static final String TS_KV_LATEST_OLD = "ts_kv_latest_old;"; - @Value("${spring.datasource.username}") - private String dbUserName; + private static final String CREATE_PARTITION_TS_KV_TABLE = "create_partition_ts_kv_table()"; + private static final String CREATE_NEW_TS_KV_LATEST_TABLE = "create_new_ts_kv_latest_table()"; + private static final String CREATE_PARTITIONS = "create_partitions()"; + private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; + private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; + private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; + private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; - @Value("${spring.datasource.password}") - private String dbPassword; + private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE; + private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE; + private static final String CALL_CREATE_PARTITIONS = CALL_REGEX + CREATE_PARTITIONS; + private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; + private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV; + private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; - @Autowired - private InstallScripts installScripts; + private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD; + private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD; + + private static final String DROP_FUNCTION_CHECK_VERSION = DROP_FUNCTION_IF_EXISTS + CHECK_VERSION; + private static final String DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE; + private static final String DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE; + private static final String DROP_FUNCTION_CREATE_PARTITIONS = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITIONS; + private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY; + private static final String DROP_FUNCTION_INSERT_INTO_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV; + private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -80,15 +81,26 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { } else { log.info("PostgreSQL version is valid!"); log.info("Updating schema ..."); - executeFunction(conn, CREATE_PARTITION_TS_KV_TABLE); - executeFunction(conn, CREATE_PARTITIONS); - executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); - executeFunction(conn, INSERT_INTO_DICTIONARY); - executeFunction(conn, INSERT_INTO_TS_KV); - executeFunction(conn, CREATE_NEW_TS_KV_LATEST_TABLE); - executeFunction(conn, INSERT_INTO_TS_KV_LATEST); - dropOldTable(conn, DROP_TABLE_TS_KV_OLD); - dropOldTable(conn, DROP_TABLE_TS_KV_LATEST_OLD); + executeFunction(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); + executeFunction(conn, CALL_CREATE_PARTITIONS); + executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); + executeFunction(conn, CALL_INSERT_INTO_DICTIONARY); + executeFunction(conn, CALL_INSERT_INTO_TS_KV); + executeFunction(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); + executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST); + + executeDropStatement(conn, DROP_TABLE_TS_KV_OLD); + executeDropStatement(conn, DROP_TABLE_TS_KV_LATEST_OLD); + + executeDropStatement(conn, DROP_FUNCTION_CHECK_VERSION); + executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE); + executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITIONS); + executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV); + executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); + log.info("schema timeseries updated!"); } } @@ -98,7 +110,7 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { } } - private void loadSql(Connection conn) { + protected void loadSql(Connection conn) { Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); try { loadFunctions(schemaUpdateFile, conn); @@ -107,45 +119,4 @@ public class PsqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage()); } } - - private void loadFunctions(Path sqlFile, Connection conn) throws Exception { - String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); - conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } - - private boolean checkVersion(Connection conn) { - log.info("Check the current PostgreSQL version..."); - boolean versionValid = false; - try { - CallableStatement callableStatement = conn.prepareCall("{? = " + CHECK_VERSION + " }"); - callableStatement.registerOutParameter(1, Types.BOOLEAN); - callableStatement.execute(); - versionValid = callableStatement.getBoolean(1); - callableStatement.close(); - } catch (Exception e) { - log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); - } - return versionValid; - } - - private void executeFunction(Connection conn, String query) { - log.info("{} ... ", query); - try { - CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); - callableStatement.execute(); - callableStatement.close(); - log.info("Successfully executed: {}", query.replace(CALL_REGEX, "")); - } catch (Exception e) { - log.info("Failed to execute {} due to: {}", query, e.getMessage()); - } - } - - private void dropOldTable(Connection conn, String query) { - try { - conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - Thread.sleep(5000); - } catch (InterruptedException | SQLException e) { - log.info("Failed to drop table {} due to: {}", query.replace("DROP TABLE ", ""), e.getMessage()); - } - } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java index dda5d2244b..b8c0d964e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java @@ -32,13 +32,13 @@ public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchema private static final String SQL_DIR = "sql"; @Value("${spring.datasource.url}") - private String dbUrl; + protected String dbUrl; @Value("${spring.datasource.username}") - private String dbUserName; + protected String dbUserName; @Value("${spring.datasource.password}") - private String dbPassword; + protected String dbPassword; @Autowired private InstallScripts installScripts; diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseSchemaService.java deleted file mode 100644 index 1db733453b..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseSchemaService.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright © 2016-2020 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.install; - -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.util.SqlDao; -import org.thingsboard.server.dao.util.TimescaleDBTsDao; - -@Service -@TimescaleDBTsDao -@Profile("install") -public class SqlTimescaleDatabaseSchemaService extends SqlAbstractDatabaseSchemaService - implements TsDatabaseSchemaService { - public SqlTimescaleDatabaseSchemaService() { - super("schema-timescale.sql", "schema-timescale-idx.sql"); - } -} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java deleted file mode 100644 index aa592853e4..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTimescaleDatabaseUpgradeService.java +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright © 2016-2020 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.install; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.util.PsqlDao; -import org.thingsboard.server.dao.util.TimescaleDBTsDao; - -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.sql.CallableStatement; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.sql.Types; - -@Service -@Profile("install") -@Slf4j -@TimescaleDBTsDao -@PsqlDao -public class SqlTimescaleDatabaseUpgradeService implements DatabaseTsUpgradeService { - - private static final String CALL_REGEX = "call "; - private static final String LOAD_FUNCTIONS_SQL = "schema_update_timescale_ts.sql"; - private static final String CHECK_VERSION = CALL_REGEX + "check_version()"; - private static final String CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + "create_ts_kv_latest_table()"; - private static final String CREATE_TENANT_TS_KV_TABLE_COPY = CALL_REGEX + "create_tenant_ts_kv_table_copy()"; - private static final String CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + "create_ts_kv_dictionary_table()"; - private static final String INSERT_INTO_DICTIONARY = CALL_REGEX + "insert_into_dictionary()"; - private static final String INSERT_INTO_TS_KV = CALL_REGEX + "insert_into_tenant_ts_kv()"; - private static final String INSERT_INTO_TS_KV_LATEST = CALL_REGEX + "insert_into_ts_kv_latest()"; - private static final String DROP_OLD_TS_KV_TABLE = "DROP TABLE tenant_ts_kv_old;"; - - @Value("${spring.datasource.url}") - private String dbUrl; - - @Value("${spring.datasource.username}") - private String dbUserName; - - @Value("${spring.datasource.password}") - private String dbPassword; - - @Autowired - private InstallScripts installScripts; - - @Override - public void upgradeDatabase(String fromVersion) throws Exception { - switch (fromVersion) { - case "2.4.3": - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { - log.info("Updating timescale schema ..."); - log.info("Load upgrade functions ..."); - loadSql(conn); - boolean versionValid = checkVersion(conn); - if (!versionValid) { - log.info("PostgreSQL version should be at least more than 9.6!"); - log.info("Please upgrade your PostgreSQL and restart the script!"); - } else { - log.info("PostgreSQL version is valid!"); - log.info("Updating schema ..."); - executeFunction(conn, CREATE_TS_KV_LATEST_TABLE); - executeFunction(conn, CREATE_TENANT_TS_KV_TABLE_COPY); - executeFunction(conn, CREATE_TS_KV_DICTIONARY_TABLE); - executeFunction(conn, INSERT_INTO_DICTIONARY); - executeFunction(conn, INSERT_INTO_TS_KV); - executeFunction(conn, INSERT_INTO_TS_KV_LATEST); - executeQuery(conn, DROP_OLD_TS_KV_TABLE); - log.info("schema timeseries updated!"); - } - } - break; - default: - throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); - } - } - - private void loadSql(Connection conn) { - Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); - try { - loadFunctions(schemaUpdateFile, conn); - log.info("Upgrade functions successfully loaded!"); - } catch (Exception e) { - log.info("Failed to load Timescale upgrade functions due to: {}", e.getMessage()); - } - } - - private void loadFunctions(Path sqlFile, Connection conn) throws Exception { - String sql = new String(Files.readAllBytes(sqlFile), StandardCharsets.UTF_8); - conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } - - private boolean checkVersion(Connection conn) { - log.info("Check the current PostgreSQL version..."); - boolean versionValid = false; - try { - CallableStatement callableStatement = conn.prepareCall("{? = " + CHECK_VERSION + " }"); - callableStatement.registerOutParameter(1, Types.BOOLEAN); - callableStatement.execute(); - versionValid = callableStatement.getBoolean(1); - callableStatement.close(); - } catch (Exception e) { - log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); - } - return versionValid; - } - - private void executeFunction(Connection conn, String query) { - log.info("{} ... ", query); - try { - CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); - callableStatement.execute(); - callableStatement.close(); - log.info("Successfully executed: {}", query.replace(CALL_REGEX, "")); - } catch (Exception e) { - log.info("Failed to execute {} due to: {}", query, e.getMessage()); - } - } - - private void executeQuery(Connection conn, String query) { - try { - conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - Thread.sleep(5000); - } catch (InterruptedException | SQLException e) { - log.info("Failed to drop table {} due to: {}", query.replace("DROP TABLE ", ""), e.getMessage()); - } - } -} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java new file mode 100644 index 0000000000..92a0a837fa --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.TimescaleDBTsDao; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; + +@Service +@TimescaleDBTsDao +@PsqlDao +@Profile("install") +@Slf4j +public class TimescaleTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { + + private static final String QUERY = "query: {}"; + private static final String SUCCESSFULLY_EXECUTED = "Successfully executed "; + private static final String FAILED_TO_EXECUTE = "Failed to execute "; + private static final String FAILED_DUE_TO = " due to: {}"; + + private static final String SUCCESSFULLY_EXECUTED_QUERY = SUCCESSFULLY_EXECUTED + QUERY; + private static final String FAILED_TO_EXECUTE_QUERY = FAILED_TO_EXECUTE + QUERY + FAILED_DUE_TO; + + @Value("${sql.timescale.chunk_time_interval:86400000}") + private long chunkTimeInterval; + + public TimescaleTsDatabaseSchemaService() { + super("schema-timescale.sql", "schema-timescale-idx.sql"); + } + + @Override + public void createDatabaseSchema() throws Exception { + super.createDatabaseSchema(); + executeQuery("SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); + } + + private void executeQuery(String query) { + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + log.info(SUCCESSFULLY_EXECUTED_QUERY, query); + Thread.sleep(5000); + } catch (InterruptedException | SQLException e) { + log.info(FAILED_TO_EXECUTE_QUERY, query, e.getMessage()); + } + } + + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java new file mode 100644 index 0000000000..84adbbc140 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -0,0 +1,125 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.TimescaleDBTsDao; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; + +@Service +@Profile("install") +@Slf4j +@TimescaleDBTsDao +@PsqlDao +public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeService implements DatabaseTsUpgradeService { + + @Value("${sql.timescale.chunk_time_interval:86400000}") + private long chunkTimeInterval; + + private static final String LOAD_FUNCTIONS_SQL = "schema_update_timescale_ts.sql"; + + private static final String TENANT_TS_KV_OLD_TABLE = "tenant_ts_kv_old;"; + + private static final String CREATE_TS_KV_LATEST_TABLE = "create_ts_kv_latest_table()"; + private static final String CREATE_NEW_TENANT_TS_KV_TABLE = "create_new_tenant_ts_kv_table()"; + private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; + private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; + private static final String INSERT_INTO_TENANT_TS_KV = "insert_into_tenant_ts_kv()"; + private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; + + private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE; + private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TENANT_TS_KV_TABLE; + private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; + private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TENANT_TS_KV; + private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; + + private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE; + + private static final String DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_LATEST_TABLE; + private static final String DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TENANT_TS_KV_TABLE; + private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY; + private static final String DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TENANT_TS_KV; + private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; + + @Autowired + private InstallScripts installScripts; + + @Override + public void upgradeDatabase(String fromVersion) throws Exception { + switch (fromVersion) { + case "2.4.3": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating timescale schema ..."); + log.info("Load upgrade functions ..."); + loadSql(conn); + boolean versionValid = checkVersion(conn); + if (!versionValid) { + log.info("PostgreSQL version should be at least more than 9.6!"); + log.info("Please upgrade your PostgreSQL and restart the script!"); + } else { + log.info("PostgreSQL version is valid!"); + log.info("Updating schema ..."); + executeFunction(conn, CALL_CREATE_TS_KV_LATEST_TABLE); + executeFunction(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); + + executeQuery(conn, "SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); + + executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); + executeFunction(conn, CALL_INSERT_INTO_DICTIONARY); + executeFunction(conn, CALL_INSERT_INTO_TS_KV); + executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST); + + //executeQuery(conn, "SELECT set_chunk_time_interval('tenant_ts_kv', " + chunkTimeInterval +");"); + + executeDropStatement(conn, DROP_OLD_TENANT_TS_KV_TABLE); + + executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE); + executeDropStatement(conn, DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY); + executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV); + executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); + + log.info("schema timeseries updated!"); + } + } + break; + default: + throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); + } + } + + protected void loadSql(Connection conn) { + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", LOAD_FUNCTIONS_SQL); + try { + loadFunctions(schemaUpdateFile, conn); + log.info("Upgrade functions successfully loaded!"); + } catch (Exception e) { + log.info("Failed to load Timescale upgrade functions due to: {}", e.getMessage()); + } + } +} \ No newline at end of file diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 7015b949b5..3aaf47c524 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -210,8 +210,12 @@ sql: stats_print_interval_ms: "${SQL_TS_LATEST_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}" - # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE - ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" + postgres: + # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. + ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" + timescale: + # Specify Interval size for new data chunks storage. + chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}" # Actor system parameters actors: diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractPsqlHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractPsqlHsqlTimeseriesDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 14d651df47..cacf7aea93 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractPsqlHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -35,7 +35,7 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j -public abstract class AbstractPsqlHsqlTimeseriesDao extends AbstractSqlTimeseriesDao { +public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSqlTimeseriesDao { @Autowired protected InsertTsRepository insertRepository; @@ -65,7 +65,7 @@ public abstract class AbstractPsqlHsqlTimeseriesDao> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); - protected void switchAgregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + protected void switchAggregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { switch (aggregation) { case AVG: findAvg(tenantId, entityId, key, startTs, endTs, entitiesFutures); 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 58b222ccde..63893c3a5e 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 @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; @@ -76,7 +75,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx private SearchTsKvLatestRepository searchTsKvLatestRepository; @Autowired - private InsertLatestRepository insertLatestRepository; + private InsertLatestTsRepository insertLatestTsRepository; @Autowired private TsKvDictionaryRepository dictionaryRepository; @@ -113,7 +112,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx .statsPrintIntervalMs(tsLatestStatsPrintIntervalMs) .build(); tsLatestQueue = new TbSqlBlockingQueue<>(tsLatestParams); - tsLatestQueue.init(logExecutor, v -> insertLatestRepository.saveOrUpdate(v)); + tsLatestQueue.init(logExecutor, v -> insertLatestTsRepository.saveOrUpdate(v)); } @PreDestroy diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java index 1e1aede157..c7b0f68b7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java @@ -19,7 +19,7 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import java.util.List; -public interface InsertLatestRepository { +public interface InsertLatestTsRepository { void saveOrUpdate(List entities); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java index 91f431ec5e..2d10f35cd9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java @@ -34,7 +34,7 @@ import java.util.List; @HsqlDao @Repository @Transactional -public class HsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository { +public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { private static final String INSERT_OR_UPDATE = "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java index 3a735acb5f..b3c71adb67 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractPsqlHsqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.EntityContainer; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.HsqlDao; @@ -46,7 +46,7 @@ import java.util.concurrent.CompletableFuture; @Slf4j @SqlTsDao @HsqlDao -public class JpaHsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao implements TimeseriesDao { +public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { @Autowired private TsKvHsqlRepository tsKvRepository; @@ -150,7 +150,7 @@ public class JpaHsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { List> entitiesFutures = new ArrayList<>(); - switchAgregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); + switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); return Futures.transform(setFutures(entitiesFutures), entity -> { if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java index 9a50cd15c4..931eb86689 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java @@ -20,7 +20,7 @@ import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; -import org.thingsboard.server.dao.sqlts.InsertLatestRepository; +import org.thingsboard.server.dao.sqlts.InsertLatestTsRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @@ -33,7 +33,7 @@ import java.util.List; @HsqlDao @Repository @Transactional -public class HsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { +public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { private static final String INSERT_OR_UPDATE = "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java index 95c88926cf..16a4c4ccd8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java @@ -22,7 +22,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; -import org.thingsboard.server.dao.sqlts.InsertLatestRepository; +import org.thingsboard.server.dao.sqlts.InsertLatestTsRepository; import org.thingsboard.server.dao.util.PsqlTsAnyDao; import java.sql.PreparedStatement; @@ -35,7 +35,7 @@ import java.util.List; @PsqlTsAnyDao @Repository @Transactional -public class PsqlLatestInsertRepository extends AbstractInsertRepository implements InsertLatestRepository { +public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { private static final String BATCH_UPDATE = "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_id = ? and key = ?"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index bcbcc2762d..f598742713 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractPsqlHsqlTimeseriesDao; +import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.EntityContainer; import org.thingsboard.server.dao.timeseries.PsqlPartition; import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; @@ -59,7 +59,7 @@ import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_STA @Slf4j @SqlTsDao @PsqlDao -public class JpaPsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao implements TimeseriesDao { +public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { private final Map partitions = new ConcurrentHashMap<>(); private static final ReentrantLock partitionCreationLock = new ReentrantLock(); @@ -73,7 +73,7 @@ public class JpaPsqlTimeseriesDao extends AbstractPsqlHsqlTimeseriesDao> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { List> entitiesFutures = new ArrayList<>(); - switchAgregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); + switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); return Futures.transform(setFutures(entitiesFutures), entity -> { if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java index b1aaff4ec8..e9cf5c9b03 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlTimeseriesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java @@ -37,7 +37,7 @@ import java.util.Map; @PsqlDao @Repository @Transactional -public class PsqlTimeseriesInsertRepository extends AbstractInsertRepository implements InsertTsRepository { +public class PsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_"; 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/TimescaleInsertTsRepository.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java index 35f0fd497a..a6fad65fbf 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/TimescaleInsertTsRepository.java @@ -34,7 +34,7 @@ import java.util.List; @PsqlDao @Repository @Transactional -public class TimescaleInsertRepository extends AbstractInsertRepository implements InsertTsRepository { +public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { 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(?, ?, ?, ?, ?, ?, ?, ?) " + 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 a57e328bc2..f7e51ef18c 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 @@ -117,7 +117,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } private ListenableFuture>> findAllAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { - CompletableFuture> listCompletableFuture = switchAgregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); + CompletableFuture> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); SettableFuture> listenableFuture = SettableFuture.create(); listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { if (throwable != null) { @@ -213,7 +213,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements return service.submit(() -> null); } - private CompletableFuture> switchAgregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) { + private CompletableFuture> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) { switch (aggregation) { case AVG: return findAvg(key, startTs, endTs, timeBucket, entityId, tenantId); diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index 4cec6ec13b..e8cf0de263 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -43,6 +43,4 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest ( long_v bigint, dbl_v double precision, CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) -); - -SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); \ No newline at end of file +); \ No newline at end of file From cd3de7cbe7c76ae77473896c17d33fe8dcba11ec Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 10 Feb 2020 16:22:48 +0200 Subject: [PATCH 189/261] Added enable ingress controller command --- k8s/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/k8s/README.md b/k8s/README.md index e57790ffc4..2a62329219 100644 --- a/k8s/README.md +++ b/k8s/README.md @@ -9,6 +9,15 @@ You need to have a Kubernetes cluster, and the kubectl command-line tool must be If you do not already have a cluster, you can create one by using [Minikube](https://kubernetes.io/docs/setup/minikube), or you can choose any other available [Kubernetes cluster deployment solutions](https://kubernetes.io/docs/setup/pick-right-solution/). +### Enable ingress addon + +By default ingress addon is disable in the Minikube, and available only in cluster providers. +To enable ingress, please execute next command: + +` +$ minikube addons enable ingress +` + ## Installation Before performing initial installation you can configure the type of database to be used with ThingsBoard. From aa0fce86258335a21e1e042d29f256f32eae215c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 12 Feb 2020 13:19:27 +0200 Subject: [PATCH 190/261] Fix for Alarm Ack/Clear/Update when Propagation flag is set and Relation Type Filter is used --- .../thingsboard/server/dao/alarm/BaseAlarmService.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) 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 00430c7dbe..d86e7e0f1b 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 @@ -386,13 +386,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) { try { List relations = relationService.findByToAsync(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM).get(); - - List propagateRelationTypes = alarm.getPropagateRelationTypes(); - Stream relationStream = relations.stream(); - if (!CollectionUtils.isEmpty(propagateRelationTypes)) { - relationStream = relationStream.filter(entityRelation -> propagateRelationTypes.contains(entityRelation.getType())); - } - Set parents = relationStream.map(EntityRelation::getFrom).collect(Collectors.toSet()); + Set parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet()); for (EntityId parentId : parents) { updateAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), oldStatus, newStatus); } From 03f5375a02acb3bcca7039e872165768529c47db Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 14 Feb 2020 19:18:18 +0200 Subject: [PATCH 191/261] JSON support (#2415) * Created JsonDataEntry and added DataType JSON * Added json to ts and attributes, created sql schema-entities-hsql.sql (json_v varchar) * refactored * refactored * added json array support * Aggregation improvement * Changed in JsonDataEntry value type from JsonNode to String * fix AggregatePartitionsFunction Co-authored-by: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> --- .../device/DeviceActorMessageProcessor.java | 7 + .../server/controller/BaseController.java | 2 + .../controller/TelemetryController.java | 43 ++- .../DefaultTelemetrySubscriptionService.java | 26 +- application/src/main/proto/cluster.proto | 1 + .../controller/ControllerSqlTestSuite.java | 2 +- .../server/mqtt/MqttSqlTestSuite.java | 2 +- .../server/rules/RuleEngineSqlTestSuite.java | 2 +- .../server/system/SystemSqlTestSuite.java | 2 +- .../SearchTextBasedWithAdditionalInfo.java | 3 +- .../common/data/kv/BaseAttributeKvEntry.java | 7 + .../server/common/data/kv/BasicKvEntry.java | 5 + .../server/common/data/kv/BasicTsKvEntry.java | 5 + .../server/common/data/kv/DataType.java | 2 +- .../server/common/data/kv/JsonDataEntry.java | 69 +++++ .../server/common/data/kv/KvEntry.java | 2 + .../transport/adaptor/JsonConverter.java | 40 ++- .../src/main/proto/transport.proto | 2 + .../CassandraBaseAttributesDao.java | 40 +-- .../server/dao/model/ModelConstants.java | 17 +- .../dao/model/sql/AbstractTsKvEntity.java | 8 +- .../dao/model/sql/AttributeKvEntity.java | 8 + .../dao/model/sqlts/hsql/TsKvEntity.java | 18 +- .../model/sqlts/latest/TsKvLatestEntity.java | 6 +- .../dao/model/sqlts/psql/TsKvEntity.java | 15 +- .../sqlts/timescale/TimescaleTsKvEntity.java | 17 +- .../AttributeKvInsertRepository.java | 118 ++------ .../HsqlAttributesInsertRepository.java | 34 +-- .../dao/sql/attributes/JpaAttributeDao.java | 1 + .../PsqlAttributesInsertRepository.java | 19 -- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 2 + .../sqlts/hsql/HsqlInsertTsRepository.java | 12 +- .../dao/sqlts/hsql/TsKvHsqlRepository.java | 3 +- .../latest/HsqlLatestInsertTsRepository.java | 8 +- .../latest/PsqlLatestInsertTsRepository.java | 33 ++- .../latest/SearchTsKvLatestRepository.java | 3 +- .../sqlts/latest/TsKvLatestRepository.java | 4 - .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 1 + .../sqlts/psql/PsqlInsertTsRepository.java | 21 +- .../dao/sqlts/psql/TsKvPsqlRepository.java | 3 +- .../timescale/AggregationRepository.java | 9 +- .../TimescaleInsertTsRepository.java | 19 +- .../timescale/TimescaleTimeseriesDao.java | 2 + .../AggregatePartitionsFunction.java | 48 +++- .../CassandraBaseTimeseriesDao.java | 59 ++-- .../resources/cassandra/schema-entities.cql | 1 + .../main/resources/cassandra/schema-ts.cql | 2 + .../resources/sql/schema-entities-hsql.sql | 251 ++++++++++++++++++ .../main/resources/sql/schema-entities.sql | 1 + .../main/resources/sql/schema-timescale.sql | 2 + dao/src/main/resources/sql/schema-ts-hsql.sql | 2 + dao/src/main/resources/sql/schema-ts-psql.sql | 4 +- .../server/dao/JpaDaoTestSuite.java | 2 +- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../metadata/TbAbstractGetAttributesNode.java | 11 +- .../engine/metadata/TbGetTelemetryNode.java | 9 + 56 files changed, 713 insertions(+), 324 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java create mode 100644 dao/src/main/resources/sql/schema-entities-hsql.sql 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 7eb91847cf..1be7e18aab 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 @@ -567,6 +567,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { case STRING_V: json.addProperty(kv.getKey(), kv.getStringV()); break; + case JSON_V: + json.add(kv.getKey(), jsonParser.parse(kv.getJsonV())); + break; } } return json; @@ -643,6 +646,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { builder.setType(KeyValueType.STRING_V); builder.setStringV(kvEntry.getStrValue().get()); break; + case JSON: + builder.setType(KeyValueType.JSON_V); + builder.setJsonV(kvEntry.getJsonValue().get()); + break; } return builder.build(); } 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 124a6e7ba1..634a78de27 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -642,6 +642,8 @@ public abstract class BaseController { entityNode.put(attr.getKey(), attr.getDoubleValue().get()); } else if (attr.getDataType() == DataType.LONG) { entityNode.put(attr.getKey(), attr.getLongValue().get()); + } else if (attr.getDataType() == DataType.JSON) { + entityNode.set(attr.getKey(), json.readTree(attr.getJsonValue().get())); } else { entityNode.put(attr.getKey(), attr.getValueAsString()); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 43b525021e..86a2457e99 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -15,12 +15,15 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -56,8 +59,10 @@ import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -77,6 +82,7 @@ import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -107,6 +113,8 @@ public class TelemetryController extends BaseController { private ExecutorService executor; + private static final ObjectMapper mapper = new ObjectMapper(); + @PostConstruct public void initExecutor() { executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller")); @@ -284,8 +292,7 @@ public class TelemetryController extends BaseController { if (startTs == null || endTs == null) { deleteToTs = endTs; return getImmediateDeferredResult("When deleteAllDataForKeys is false, start and end timestamp values shouldn't be empty", HttpStatus.BAD_REQUEST); - } - else{ + } else { deleteFromTs = startTs; deleteToTs = endTs; } @@ -536,8 +543,9 @@ public class TelemetryController extends BaseController { return new FutureCallback>() { @Override public void onSuccess(List attributes) { - List values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), - attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); + List values = attributes.stream().map(attribute -> + new AttributeData(attribute.getLastUpdateTs(), attribute.getKey(), getKvValue(attribute)) + ).collect(Collectors.toList()); logAttributesRead(user, entityId, scope, keyList, null); response.setResult(new ResponseEntity<>(values, HttpStatus.OK)); } @@ -639,7 +647,9 @@ public class TelemetryController extends BaseController { jsonNode.fields().forEachRemaining(entry -> { String key = entry.getKey(); JsonNode value = entry.getValue(); - if (entry.getValue().isTextual()) { + if (entry.getValue().isObject() || entry.getValue().isArray()) { + attributes.add(new BaseAttributeKvEntry(new JsonDataEntry(key, toJsonStr(value)), ts)); + } else if (entry.getValue().isTextual()) { if (maxStringValueLength > 0 && entry.getValue().textValue().length() > maxStringValueLength) { String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", entry.getValue().textValue().length(), key, maxStringValueLength); throw new UncheckedApiException(new InvalidParametersException(message)); @@ -659,4 +669,27 @@ public class TelemetryController extends BaseController { }); return attributes; } + + private String toJsonStr(JsonNode value) { + try { + return mapper.writeValueAsString(value); + } catch (JsonProcessingException e) { + throw new JsonParseException("Can't parse jsonValue: " + value, e); + } + } + + private JsonNode toJsonNode(String value) { + try { + return mapper.readTree(value); + } catch (IOException e) { + throw new JsonParseException("Can't parse jsonValue: " + value, e); + } + } + + private Object getKvValue(KvEntry entry) { + if (entry.getDataType() == DataType.JSON) { + return toJsonNode(entry.getJsonValue().get()); + } + return entry.getValue(); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 45134a6bdd..d29e5de5b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -24,9 +24,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; -import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; @@ -36,7 +36,20 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.*; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.dao.attributes.AttributesService; @@ -105,7 +118,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @Autowired @Lazy private ActorService actorService; - + private ExecutorService tsCallBackExecutor; private ExecutorService wsCallBackExecutor; @@ -692,6 +705,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio Optional doubleValue = attr.getDoubleValue(); doubleValue.ifPresent(dataBuilder::setDoubleValue); break; + case JSON: + Optional jsonValue = attr.getJsonValue(); + jsonValue.ifPresent(dataBuilder::setJsonValue); + break; case STRING: Optional stringValue = attr.getStrValue(); stringValue.ifPresent(dataBuilder::setStrValue); @@ -724,6 +741,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio case STRING: entry = new StringDataEntry(proto.getKey(), proto.getStrValue()); break; + case JSON: + entry = new JsonDataEntry(proto.getKey(), proto.getJsonValue()); + break; } return entry; } diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto index b4ebc52f5e..cfacc66121 100644 --- a/application/src/main/proto/cluster.proto +++ b/application/src/main/proto/cluster.proto @@ -125,6 +125,7 @@ message KeyValueProto { int64 longValue = 5; double doubleValue = 6; bool boolValue = 7; + string jsonValue = 8; } message FromDeviceRPCResponseProto { diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 4fe33e4716..8dc0acff57 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -30,7 +30,7 @@ public class ControllerSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java index 5fb8c4d0c7..2863589ba1 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java @@ -29,7 +29,7 @@ public class MqttSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java index ce2c6852be..5f930821f7 100644 --- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java @@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); } diff --git a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java index 3cbb7d9773..b12d513ce0 100644 --- a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java @@ -31,7 +31,7 @@ public class SystemSqlTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java index ecbd2c73f5..8dc9bf6abc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java @@ -35,6 +35,7 @@ import java.util.function.Consumer; @Slf4j public abstract class SearchTextBasedWithAdditionalInfo extends SearchTextBased implements HasAdditionalInfo { + private static final ObjectMapper mapper = new ObjectMapper(); private transient JsonNode additionalInfo; @JsonIgnore private byte[] additionalInfoBytes; @@ -97,7 +98,7 @@ public abstract class SearchTextBasedWithAdditionalInfo ext public static void setJson(JsonNode json, Consumer jsonConsumer, Consumer bytesConsumer) { jsonConsumer.accept(json); try { - bytesConsumer.accept(new ObjectMapper().writeValueAsBytes(json)); + bytesConsumer.accept(mapper.writeValueAsBytes(json)); } catch (JsonProcessingException e) { log.warn("Can't serialize json data: ", e); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java index ac8a5c2f78..5639f98d01 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.kv; +import com.fasterxml.jackson.databind.JsonNode; + import java.util.Optional; /** @@ -65,6 +67,11 @@ public class BaseAttributeKvEntry implements AttributeKvEntry { return kv.getDoubleValue(); } + @Override + public Optional getJsonValue() { + return kv.getJsonValue(); + } + @Override public String getValueAsString() { return kv.getValueAsString(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java index a41bf6b232..7bc92ff74c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java @@ -51,6 +51,11 @@ public abstract class BasicKvEntry implements KvEntry { return Optional.ofNullable(null); } + @Override + public Optional getJsonValue() { + return Optional.ofNullable(null); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java index f7628da74c..c2d6688004 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -58,6 +58,11 @@ public class BasicTsKvEntry implements TsKvEntry { return kv.getDoubleValue(); } + @Override + public Optional getJsonValue() { + return kv.getJsonValue(); + } + @Override public Object getValue() { return kv.getValue(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java index 84f918ede3..3571b0c882 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java @@ -17,6 +17,6 @@ package org.thingsboard.server.common.data.kv; public enum DataType { - STRING, LONG, BOOLEAN, DOUBLE; + STRING, LONG, BOOLEAN, DOUBLE, JSON; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java new file mode 100644 index 0000000000..0510f311d5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java @@ -0,0 +1,69 @@ +/** + * Copyright © 2016-2020 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.common.data.kv; + +import java.util.Objects; +import java.util.Optional; + +public class JsonDataEntry extends BasicKvEntry { + private final String value; + + public JsonDataEntry(String key, String value) { + super(key); + this.value = value; + } + + @Override + public DataType getDataType() { + return DataType.JSON; + } + + @Override + public Optional getJsonValue() { + return Optional.ofNullable(value); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof JsonDataEntry)) return false; + if (!super.equals(o)) return false; + JsonDataEntry that = (JsonDataEntry) o; + return Objects.equals(value, that.value); + } + + @Override + public Object getValue() { + return value; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), value); + } + + @Override + public String toString() { + return "JsonDataEntry{" + + "value=" + value + + "} " + super.toString(); + } + + @Override + public String getValueAsString() { + return value; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java index c8753fda8b..296ddd37aa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java @@ -37,6 +37,8 @@ public interface KvEntry extends Serializable { Optional getDoubleValue(); + Optional getJsonValue(); + String getValueAsString(); Object getValue(); 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 03f840a17e..56e2c87d6e 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 @@ -31,6 +31,7 @@ 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.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -59,6 +60,7 @@ import java.util.stream.Collectors; public class JsonConverter { private static final Gson GSON = new Gson(); + private static final JsonParser JSON_PARSER = new JsonParser(); private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; private static final String DEVICE_PROPERTY = "device"; @@ -204,6 +206,14 @@ public class JsonConverter { } else if (!value.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } + } else if (element.isJsonObject() || element.isJsonArray()) { + result.add(KeyValueProto + .newBuilder() + .setKey(valueEntry + .getKey()) + .setType(KeyValueType.JSON_V) + .setJsonV(element.toString()) + .build()); } else if (!element.isJsonNull()) { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } @@ -354,6 +364,9 @@ public class JsonConverter { case LONG_V: json.addProperty(name, entry.getLongV()); break; + case JSON_V: + json.add(name, JSON_PARSER.parse(entry.getJsonV())); + break; } } @@ -363,47 +376,48 @@ public class JsonConverter { private static Consumer addToObjectFromProto(JsonObject result) { return de -> { - JsonPrimitive value; switch (de.getKv().getType()) { case BOOLEAN_V: - value = new JsonPrimitive(de.getKv().getBoolV()); + result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getBoolV())); break; case DOUBLE_V: - value = new JsonPrimitive(de.getKv().getDoubleV()); + result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getDoubleV())); break; case LONG_V: - value = new JsonPrimitive(de.getKv().getLongV()); + result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getLongV())); break; case STRING_V: - value = new JsonPrimitive(de.getKv().getStringV()); + result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getStringV())); break; + case JSON_V: + result.add(de.getKv().getKey(), JSON_PARSER.parse(de.getKv().getJsonV())); default: throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType()); } - result.add(de.getKv().getKey(), value); }; } private static Consumer addToObject(JsonObject result) { return de -> { - JsonPrimitive value; switch (de.getDataType()) { case BOOLEAN: - value = new JsonPrimitive(de.getBooleanValue().get()); + result.add(de.getKey(), new JsonPrimitive(de.getBooleanValue().get())); break; case DOUBLE: - value = new JsonPrimitive(de.getDoubleValue().get()); + result.add(de.getKey(), new JsonPrimitive(de.getDoubleValue().get())); break; case LONG: - value = new JsonPrimitive(de.getLongValue().get()); + result.add(de.getKey(), new JsonPrimitive(de.getLongValue().get())); break; case STRING: - value = new JsonPrimitive(de.getStrValue().get()); + result.add(de.getKey(), new JsonPrimitive(de.getStrValue().get())); + break; + case JSON: + result.add(de.getKey(), JSON_PARSER.parse(de.getJsonValue().get())); break; default: throw new IllegalArgumentException("Unsupported data type: " + de.getDataType()); } - result.add(de.getKey(), value); }; } @@ -464,6 +478,8 @@ public class JsonConverter { } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } + } else if (element.isJsonObject() || element.isJsonArray()) { + result.add(new JsonDataEntry(valueEntry.getKey(), element.toString())); } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); } diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto index 2d536b1769..e8b513574a 100644 --- a/common/transport/transport-api/src/main/proto/transport.proto +++ b/common/transport/transport-api/src/main/proto/transport.proto @@ -47,6 +47,7 @@ enum KeyValueType { LONG_V = 1; DOUBLE_V = 2; STRING_V = 3; + JSON_V = 4; } message KeyValueProto { @@ -56,6 +57,7 @@ message KeyValueProto { int64 long_v = 4; double double_v = 5; string string_v = 6; + string json_v = 7; } message TsKvProto { diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java index 93a39f8ef4..481f2ac085 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java @@ -112,31 +112,18 @@ public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implem @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { - BoundStatement stmt = getSaveStmt().bind(); - stmt.setString(0, entityId.getEntityType().name()); - stmt.setUUID(1, entityId.getId()); - stmt.setString(2, attributeType); - stmt.setString(3, attribute.getKey()); - stmt.setLong(4, attribute.getLastUpdateTs()); - stmt.setString(5, attribute.getStrValue().orElse(null)); - Optional booleanValue = attribute.getBooleanValue(); - if (booleanValue.isPresent()) { - stmt.setBool(6, booleanValue.get()); - } else { - stmt.setToNull(6); - } - Optional longValue = attribute.getLongValue(); - if (longValue.isPresent()) { - stmt.setLong(7, longValue.get()); - } else { - stmt.setToNull(7); - } - Optional doubleValue = attribute.getDoubleValue(); - if (doubleValue.isPresent()) { - stmt.setDouble(8, doubleValue.get()); - } else { - stmt.setToNull(8); - } + BoundStatement stmt = getSaveStmt().bind() + .setString(0, entityId.getEntityType().name()) + .setUUID(1, entityId.getId()) + .setString(2, attributeType) + .setString(3, attribute.getKey()) + .setLong(4, attribute.getLastUpdateTs()) + .set(5, attribute.getStrValue().orElse(null), String.class) + .set(6, attribute.getBooleanValue().orElse(null), Boolean.class) + .set(7, attribute.getLongValue().orElse(null), Long.class) + .set(8, attribute.getDoubleValue().orElse(null), Double.class) + .set(9, attribute.getJsonValue().orElse(null), String.class); + log.trace("Generated save stmt [{}] for entityId {} and attributeType {} and attribute", stmt, entityId, attributeType, attribute); return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); } @@ -172,8 +159,9 @@ public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN + + "," + ModelConstants.JSON_VALUE_COLUMN + ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); } return saveStmt; } 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 a07c4868e6..96ce14c459 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 @@ -369,17 +369,18 @@ public class ModelConstants { public static final String STRING_VALUE_COLUMN = "str_v"; public static final String LONG_VALUE_COLUMN = "long_v"; public static final String DOUBLE_VALUE_COLUMN = "dbl_v"; + public static final String JSON_VALUE_COLUMN = "json_v"; - protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; + protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; - protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN)}; + protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN)}; - protected static final String[] MIN_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, - new String[]{min(LONG_VALUE_COLUMN), min(DOUBLE_VALUE_COLUMN), min(BOOLEAN_VALUE_COLUMN), min(STRING_VALUE_COLUMN)}); - protected static final String[] MAX_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, - new String[]{max(LONG_VALUE_COLUMN), max(DOUBLE_VALUE_COLUMN), max(BOOLEAN_VALUE_COLUMN), max(STRING_VALUE_COLUMN)}); - protected static final String[] SUM_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, - new String[]{sum(LONG_VALUE_COLUMN), sum(DOUBLE_VALUE_COLUMN)}); + protected static final String[] MIN_AGGREGATION_COLUMNS = + ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{min(LONG_VALUE_COLUMN), min(DOUBLE_VALUE_COLUMN), min(BOOLEAN_VALUE_COLUMN), min(STRING_VALUE_COLUMN), min(JSON_VALUE_COLUMN)}); + protected static final String[] MAX_AGGREGATION_COLUMNS = + ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{max(LONG_VALUE_COLUMN), max(DOUBLE_VALUE_COLUMN), max(BOOLEAN_VALUE_COLUMN), max(STRING_VALUE_COLUMN), max(JSON_VALUE_COLUMN)}); + protected static final String[] SUM_AGGREGATION_COLUMNS = + ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{sum(LONG_VALUE_COLUMN), sum(DOUBLE_VALUE_COLUMN)}); protected static final String[] AVG_AGGREGATION_COLUMNS = SUM_AGGREGATION_COLUMNS; public static String min(String s) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index d7ffc72a34..f0dd03b5ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -19,6 +19,7 @@ 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.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -29,12 +30,12 @@ import javax.persistence.Column; import javax.persistence.Id; import javax.persistence.MappedSuperclass; import javax.persistence.Transient; - import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_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; @@ -68,6 +69,9 @@ public abstract class AbstractTsKvEntity implements ToData { @Column(name = DOUBLE_VALUE_COLUMN) protected Double doubleValue; + @Column(name = JSON_VALUE_COLUMN) + protected String jsonValue; + @Transient protected String strKey; @@ -93,6 +97,8 @@ public abstract class AbstractTsKvEntity implements ToData { kvEntry = new DoubleDataEntry(strKey, doubleValue); } else if (booleanValue != null) { kvEntry = new BooleanDataEntry(strKey, booleanValue); + } else if (jsonValue != null) { + kvEntry = new JsonDataEntry(strKey, jsonValue); } return new BasicTsKvEntry(ts, kvEntry); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index 250d4325e5..f0de269cea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -20,6 +20,7 @@ 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.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -33,6 +34,7 @@ import java.io.Serializable; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LAST_UPDATE_TS_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN; @@ -57,6 +59,9 @@ public class AttributeKvEntity implements ToData, Serializable @Column(name = DOUBLE_VALUE_COLUMN) private Double doubleValue; + @Column(name = JSON_VALUE_COLUMN) + private String jsonValue; + @Column(name = LAST_UPDATE_TS_COLUMN) private Long lastUpdateTs; @@ -71,7 +76,10 @@ public class AttributeKvEntity implements ToData, Serializable kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); } else if (longValue != null) { kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); + } else if (jsonValue != null) { + kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue); } + return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java index ba24543090..a38dc185a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java @@ -16,30 +16,16 @@ package org.thingsboard.server.dao.model.sqlts.hsql; 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.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 org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; -import javax.persistence.Transient; -import java.util.UUID; - -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; @Data @@ -98,12 +84,14 @@ public final class TsKvEntity extends AbstractTsKvEntity implements ToData 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 = ? " + + private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), 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(?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " + + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json), ?) " + "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"; + "DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?;"; @Autowired protected JdbcTemplate jdbcTemplate; @@ -68,74 +60,6 @@ public abstract class AttributeKvInsertRepository { @Value("${sql.remove_null_chars}") private boolean removeNullChars; - @PersistenceContext - protected EntityManager entityManager; - - public abstract void saveOrUpdate(AttributeKvEntity entity); - - protected void processSaveOrUpdate(AttributeKvEntity 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 - private void saveOrUpdateBoolean(AttributeKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("entity_type", entity.getId().getEntityType().name()) - .setParameter("entity_id", entity.getId().getEntityId()) - .setParameter("attribute_type", entity.getId().getAttributeType()) - .setParameter("attribute_key", entity.getId().getAttributeKey()) - .setParameter("bool_v", entity.getBooleanValue()) - .setParameter("last_update_ts", entity.getLastUpdateTs()) - .executeUpdate(); - } - - @Modifying - private void saveOrUpdateString(AttributeKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("entity_type", entity.getId().getEntityType().name()) - .setParameter("entity_id", entity.getId().getEntityId()) - .setParameter("attribute_type", entity.getId().getAttributeType()) - .setParameter("attribute_key", entity.getId().getAttributeKey()) - .setParameter("str_v", replaceNullChars(entity.getStrValue())) - .setParameter("last_update_ts", entity.getLastUpdateTs()) - .executeUpdate(); - } - - @Modifying - private void saveOrUpdateLong(AttributeKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("entity_type", entity.getId().getEntityType().name()) - .setParameter("entity_id", entity.getId().getEntityId()) - .setParameter("attribute_type", entity.getId().getAttributeType()) - .setParameter("attribute_key", entity.getId().getAttributeKey()) - .setParameter("long_v", entity.getLongValue()) - .setParameter("last_update_ts", entity.getLastUpdateTs()) - .executeUpdate(); - } - - @Modifying - private void saveOrUpdateDouble(AttributeKvEntity entity, String query) { - entityManager.createNativeQuery(query) - .setParameter("entity_type", entity.getId().getEntityType().name()) - .setParameter("entity_id", entity.getId().getEntityId()) - .setParameter("attribute_type", entity.getId().getAttributeType()) - .setParameter("attribute_key", entity.getId().getAttributeKey()) - .setParameter("dbl_v", entity.getDoubleValue()) - .setParameter("last_update_ts", entity.getLastUpdateTs()) - .executeUpdate(); - } - protected void saveOrUpdate(List entities) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override @@ -164,11 +88,13 @@ public abstract class AttributeKvInsertRepository { ps.setNull(4, Types.BOOLEAN); } - ps.setLong(5, kvEntity.getLastUpdateTs()); - ps.setString(6, kvEntity.getId().getEntityType().name()); - ps.setString(7, kvEntity.getId().getEntityId()); - ps.setString(8, kvEntity.getId().getAttributeType()); - ps.setString(9, kvEntity.getId().getAttributeKey()); + ps.setString(5, replaceNullChars(kvEntity.getJsonValue())); + + ps.setLong(6, kvEntity.getLastUpdateTs()); + ps.setString(7, kvEntity.getId().getEntityType().name()); + ps.setString(8, kvEntity.getId().getEntityId()); + ps.setString(9, kvEntity.getId().getAttributeType()); + ps.setString(10, kvEntity.getId().getAttributeKey()); } @Override @@ -199,35 +125,39 @@ public abstract class AttributeKvInsertRepository { ps.setString(2, kvEntity.getId().getEntityId()); ps.setString(3, kvEntity.getId().getAttributeType()); ps.setString(4, kvEntity.getId().getAttributeKey()); + ps.setString(5, replaceNullChars(kvEntity.getStrValue())); - ps.setString(10, replaceNullChars(kvEntity.getStrValue())); + ps.setString(11, replaceNullChars(kvEntity.getStrValue())); if (kvEntity.getLongValue() != null) { ps.setLong(6, kvEntity.getLongValue()); - ps.setLong(11, kvEntity.getLongValue()); + ps.setLong(12, kvEntity.getLongValue()); } else { ps.setNull(6, Types.BIGINT); - ps.setNull(11, Types.BIGINT); + ps.setNull(12, Types.BIGINT); } if (kvEntity.getDoubleValue() != null) { ps.setDouble(7, kvEntity.getDoubleValue()); - ps.setDouble(12, kvEntity.getDoubleValue()); + ps.setDouble(13, kvEntity.getDoubleValue()); } else { ps.setNull(7, Types.DOUBLE); - ps.setNull(12, Types.DOUBLE); + ps.setNull(13, Types.DOUBLE); } if (kvEntity.getBooleanValue() != null) { ps.setBoolean(8, kvEntity.getBooleanValue()); - ps.setBoolean(13, kvEntity.getBooleanValue()); + ps.setBoolean(14, kvEntity.getBooleanValue()); } else { ps.setNull(8, Types.BOOLEAN); - ps.setNull(13, Types.BOOLEAN); + ps.setNull(14, Types.BOOLEAN); } - ps.setLong(9, kvEntity.getLastUpdateTs()); - ps.setLong(14, kvEntity.getLastUpdateTs()); + ps.setString(9, replaceNullChars(kvEntity.getJsonValue())); + ps.setString(15, replaceNullChars(kvEntity.getJsonValue())); + + ps.setLong(10, kvEntity.getLastUpdateTs()); + ps.setLong(16, kvEntity.getLastUpdateTs()); } @Override 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 8378a4488b..1d7aad384a 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 @@ -30,36 +30,16 @@ import java.util.List; @Transactional public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository { - private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.long_v = null, attribute_kv.dbl_v = null "; - private static final String ON_STR_VALUE_UPDATE_SET_NULLS = " attribute_kv.bool_v = null, attribute_kv.long_v = null, attribute_kv.dbl_v = null "; - private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.bool_v = null, attribute_kv.dbl_v = null "; - private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.long_v = null, attribute_kv.bool_v = null "; - - private static final String INSERT_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS); - private static final String INSERT_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS); - 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) " + + "MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_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); - } - - 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)"; - } - + "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.json_v = A.json_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, json_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.json_v, A.last_update_ts)"; @Override protected void saveOrUpdate(List entities) { @@ -89,7 +69,9 @@ public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository ps.setNull(8, Types.BOOLEAN); } - ps.setLong(9, entity.getLastUpdateTs()); + ps.setString(9, entity.getJsonValue()); + + ps.setLong(10, entity.getLastUpdateTs()); }); }); } 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 85a4541be2..ab9964d3e3 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 @@ -128,6 +128,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); entity.setLongValue(attribute.getLongValue().orElse(null)); entity.setBooleanValue(attribute.getBooleanValue().orElse(null)); + entity.setJsonValue(attribute.getJsonValue().orElse(null)); return addToQueue(entity); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java index 1f553ada04..020e63cd36 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java @@ -17,7 +17,6 @@ package org.thingsboard.server.dao.sql.attributes; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlDao; @@ -27,22 +26,4 @@ import org.thingsboard.server.dao.util.SqlDao; @Transactional public class PsqlAttributesInsertRepository extends AttributeKvInsertRepository { - 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); - - @Override - public void saveOrUpdate(AttributeKvEntity entity) { - processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT); - } - - private static String getInsertOrUpdateString(String value, String nullValues) { - return "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) VALUES (:entity_type, :entity_id, :attribute_type, :attribute_key, :" + value + ", :last_update_ts) ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) DO UPDATE SET " + value + " = :" + value + ", last_update_ts = :last_update_ts," + nullValues; - } } \ No newline at end of file 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 63893c3a5e..e8cbf5d73b 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 @@ -253,6 +253,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + latestEntity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); + return tsLatestQueue.add(latestEntity); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java index 2d10f35cd9..d1e5294309 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java @@ -37,14 +37,14 @@ import java.util.List; public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { private static final String INSERT_OR_UPDATE = - "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + - "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " + "ON (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_id, key, ts, bool_v, str_v, long_v, dbl_v) " + - "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; + "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 ,ts_kv.json_v = T.json_v " + + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " + + "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);"; @Override public void saveOrUpdate(List> entities) { @@ -76,6 +76,8 @@ public class HsqlInsertTsRepository extends AbstractInsertRepository implements } else { ps.setNull(7, Types.DOUBLE); } + + ps.setString(8, tsKvEntity.getJsonValue()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java index 552d515e44..a7c0effb97 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java @@ -97,7 +97,8 @@ public interface TsKvHsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") CompletableFuture findCount(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java index 931eb86689..65ac6257f0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java @@ -36,11 +36,11 @@ import java.util.List; public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { private static final String INSERT_OR_UPDATE = - "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + - "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + + "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " + + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " + "ON (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 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, ts_kv_latest.json_v = T.json_v " + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; @@ -72,6 +72,8 @@ public class HsqlLatestInsertTsRepository extends AbstractInsertRepository imple } else { ps.setNull(7, Types.DOUBLE); } + + ps.setString(8, entities.get(i).getJsonValue()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java index 16a4c4ccd8..d367f44620 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java @@ -38,12 +38,12 @@ import java.util.List; public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { private static final String BATCH_UPDATE = - "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_id = ? and key = ?"; + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? and key = ?"; private static final String INSERT_OR_UPDATE = - "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?) " + - "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + + "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; @Override public void saveOrUpdate(List entities) { @@ -76,8 +76,10 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple ps.setNull(5, Types.DOUBLE); } - ps.setObject(6, tsKvLatestEntity.getEntityId()); - ps.setInt(7, tsKvLatestEntity.getKey()); + ps.setString(6, replaceNullChars(tsKvLatestEntity.getJsonValue())); + + ps.setObject(7, tsKvLatestEntity.getEntityId()); + ps.setInt(8, tsKvLatestEntity.getKey()); } @Override @@ -106,36 +108,39 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i); ps.setObject(1, tsKvLatestEntity.getEntityId()); ps.setInt(2, tsKvLatestEntity.getKey()); + ps.setLong(3, tsKvLatestEntity.getTs()); - ps.setLong(8, tsKvLatestEntity.getTs()); + ps.setLong(9, tsKvLatestEntity.getTs()); if (tsKvLatestEntity.getBooleanValue() != null) { ps.setBoolean(4, tsKvLatestEntity.getBooleanValue()); - ps.setBoolean(9, tsKvLatestEntity.getBooleanValue()); + ps.setBoolean(10, tsKvLatestEntity.getBooleanValue()); } else { ps.setNull(4, Types.BOOLEAN); - ps.setNull(9, Types.BOOLEAN); + ps.setNull(10, Types.BOOLEAN); } ps.setString(5, replaceNullChars(tsKvLatestEntity.getStrValue())); - ps.setString(10, replaceNullChars(tsKvLatestEntity.getStrValue())); - + ps.setString(11, replaceNullChars(tsKvLatestEntity.getStrValue())); if (tsKvLatestEntity.getLongValue() != null) { ps.setLong(6, tsKvLatestEntity.getLongValue()); - ps.setLong(11, tsKvLatestEntity.getLongValue()); + ps.setLong(12, tsKvLatestEntity.getLongValue()); } else { ps.setNull(6, Types.BIGINT); - ps.setNull(11, Types.BIGINT); + ps.setNull(12, Types.BIGINT); } if (tsKvLatestEntity.getDoubleValue() != null) { ps.setDouble(7, tsKvLatestEntity.getDoubleValue()); - ps.setDouble(12, tsKvLatestEntity.getDoubleValue()); + ps.setDouble(13, tsKvLatestEntity.getDoubleValue()); } else { ps.setNull(7, Types.DOUBLE); - ps.setNull(12, Types.DOUBLE); + ps.setNull(13, Types.DOUBLE); } + + ps.setString(8, replaceNullChars(tsKvLatestEntity.getJsonValue())); + ps.setString(14, replaceNullChars(tsKvLatestEntity.getJsonValue())); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java index 5940a33d31..b5da093b6f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java @@ -29,8 +29,9 @@ import java.util.UUID; public class SearchTsKvLatestRepository { public static final String FIND_ALL_BY_ENTITY_ID = "findAllByEntityId"; + public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, ts_kv_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," + - " ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " + + " ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.json_v AS jsonValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)"; @PersistenceContext diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index 9ba59c10ef..5232b88489 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -20,11 +20,7 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.util.SqlDao; -import java.util.List; -import java.util.UUID; - @SqlDao public interface TsKvLatestRepository extends CrudRepository { - List findAllByEntityId(UUID entityId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index f598742713..6d60e843ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -104,6 +104,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); PsqlPartition psqlPartition = toPartition(tsKvEntry.getTs()); log.trace("Saving entity: {}", entity); return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate())); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java index e9cf5c9b03..caf4528812 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java @@ -41,8 +41,8 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_"; - private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES (?, ?, ?, ?, ?, ?, ?) " + - "ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; + private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES (?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + + "ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; @Override public void saveOrUpdate(List> entities) { @@ -61,30 +61,33 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements if (tsKvEntity.getBooleanValue() != null) { ps.setBoolean(4, tsKvEntity.getBooleanValue()); - ps.setBoolean(8, tsKvEntity.getBooleanValue()); + ps.setBoolean(9, tsKvEntity.getBooleanValue()); } else { ps.setNull(4, Types.BOOLEAN); - ps.setNull(8, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); } ps.setString(5, replaceNullChars(tsKvEntity.getStrValue())); - ps.setString(9, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); if (tsKvEntity.getLongValue() != null) { ps.setLong(6, tsKvEntity.getLongValue()); - ps.setLong(10, tsKvEntity.getLongValue()); + ps.setLong(11, tsKvEntity.getLongValue()); } else { ps.setNull(6, Types.BIGINT); - ps.setNull(10, Types.BIGINT); + ps.setNull(11, Types.BIGINT); } if (tsKvEntity.getDoubleValue() != null) { ps.setDouble(7, tsKvEntity.getDoubleValue()); - ps.setDouble(11, tsKvEntity.getDoubleValue()); + ps.setDouble(12, tsKvEntity.getDoubleValue()); } else { ps.setNull(7, Types.DOUBLE); - ps.setNull(11, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); + + ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue())); + ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue())); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java index 7b3328f86b..b164046bbc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java @@ -98,7 +98,8 @@ public interface TsKvPsqlRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") CompletableFuture findCount(@Param("entityId") UUID entityId, @Param("entityKey") int entityKey, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index 15deb702a7..5a0b9c6a59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -36,18 +36,17 @@ public class AggregationRepository { public static final String FIND_SUM = "findSum"; public static final String FIND_COUNT = "findCount"; - public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket"; public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; - public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, 'MAX' AS aggType "; + public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, MAX(tskv.json_v) AS jsonValue, 'MAX' AS aggType "; - public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, 'MIN' AS aggType "; + public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, MIN(tskv.json_v) AS jsonValue,'MIN' AS aggType "; - public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'SUM' AS aggType "; + public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, null AS jsonValue, 'SUM' AS aggType "; - public static final String FIND_COUNT_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount "; + public static final String FIND_COUNT_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount, SUM(CASE WHEN tskv.json_v IS NULL THEN 0 ELSE 1 END) AS jsonValueCount "; @PersistenceContext private EntityManager entityManager; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java index a6fad65fbf..6d863af105 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java @@ -37,8 +37,8 @@ import java.util.List; public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { 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 = ?;"; + "INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + + "ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; @Override public void saveOrUpdate(List> entities) { @@ -56,28 +56,31 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem ps.setBoolean(9, tsKvEntity.getBooleanValue()); } else { ps.setNull(5, Types.BOOLEAN); - ps.setNull(9, Types.BOOLEAN); + ps.setNull(10, Types.BOOLEAN); } ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); - ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(11, replaceNullChars(tsKvEntity.getStrValue())); if (tsKvEntity.getLongValue() != null) { ps.setLong(7, tsKvEntity.getLongValue()); - ps.setLong(11, tsKvEntity.getLongValue()); + ps.setLong(12, tsKvEntity.getLongValue()); } else { ps.setNull(7, Types.BIGINT); - ps.setNull(11, Types.BIGINT); + ps.setNull(12, Types.BIGINT); } if (tsKvEntity.getDoubleValue() != null) { ps.setDouble(8, tsKvEntity.getDoubleValue()); - ps.setDouble(12, tsKvEntity.getDoubleValue()); + ps.setDouble(13, tsKvEntity.getDoubleValue()); } else { ps.setNull(8, Types.DOUBLE); - ps.setNull(12, Types.DOUBLE); + ps.setNull(13, Types.DOUBLE); } + + ps.setString(9, replaceNullChars(tsKvEntity.getJsonValue())); + ps.setString(14, replaceNullChars(tsKvEntity.getJsonValue())); } @Override 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 f7e51ef18c..9f8f5c6f74 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 @@ -174,6 +174,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); + entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); + log.trace("Saving entity to timescale db: {}", entity); return tsQueue.add(new EntityContainer(entity, null)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java index d81ddc3801..6229f68818 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -41,10 +42,12 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct private static final int DOUBLE_CNT_POS = 1; private static final int BOOL_CNT_POS = 2; private static final int STR_CNT_POS = 3; - private static final int LONG_POS = 4; - private static final int DOUBLE_POS = 5; - private static final int BOOL_POS = 6; - private static final int STR_POS = 7; + private static final int JSON_CNT_POS = 4; + private static final int LONG_POS = 5; + private static final int DOUBLE_POS = 6; + private static final int BOOL_POS = 7; + private static final int STR_POS = 8; + private static final int JSON_POS = 9; private final Aggregation aggregation; private final String key; @@ -72,7 +75,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } } return processAggregationResult(aggResult); - }catch (Exception e){ + } catch (Exception e) { log.error("[{}][{}][{}] Failed to aggregate data", key, ts, aggregation, e); return Optional.empty(); } @@ -85,11 +88,13 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct Double curDValue = null; Boolean curBValue = null; String curSValue = null; + String curJValue = null; long longCount = row.getLong(LONG_CNT_POS); long doubleCount = row.getLong(DOUBLE_CNT_POS); long boolCount = row.getLong(BOOL_CNT_POS); long strCount = row.getLong(STR_CNT_POS); + long jsonCount = row.getLong(JSON_CNT_POS); if (longCount > 0 || doubleCount > 0) { if (longCount > 0) { @@ -111,6 +116,10 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct aggResult.dataType = DataType.STRING; curCount = strCount; curSValue = getStringValue(row); + } else if (jsonCount > 0) { + aggResult.dataType = DataType.JSON; + curCount = jsonCount; + curJValue = getJsonValue(row); } else { return; } @@ -120,9 +129,9 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } else if (aggregation == Aggregation.AVG || aggregation == Aggregation.SUM) { processAvgOrSumAggregation(aggResult, curCount, curLValue, curDValue); } else if (aggregation == Aggregation.MIN) { - processMinAggregation(aggResult, curLValue, curDValue, curBValue, curSValue); + processMinAggregation(aggResult, curLValue, curDValue, curBValue, curSValue, curJValue); } else if (aggregation == Aggregation.MAX) { - processMaxAggregation(aggResult, curLValue, curDValue, curBValue, curSValue); + processMaxAggregation(aggResult, curLValue, curDValue, curBValue, curSValue, curJValue); } } @@ -136,7 +145,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } } - private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { + private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue, String curJValue) { if (curDValue != null || curLValue != null) { if (curDValue != null) { aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); @@ -148,10 +157,12 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) { aggResult.sValue = curSValue; + } else if (curJValue != null && (aggResult.jValue == null || curJValue.compareTo(aggResult.jValue) < 0)) { + aggResult.jValue = curJValue; } } - private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { + private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue, String curJValue) { if (curDValue != null || curLValue != null) { if (curDValue != null) { aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); @@ -163,6 +174,8 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue; } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) { aggResult.sValue = curSValue; + } else if (curJValue != null && (aggResult.jValue == null || curJValue.compareTo(aggResult.jValue) > 0)) { + aggResult.jValue = curJValue; } } @@ -182,6 +195,14 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct } } + private String getJsonValue(Row row) { + if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX) { + return row.getString(JSON_POS); + } else { + return null; + } + } + private Long getLongValue(Row row) { if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX || aggregation == Aggregation.SUM || aggregation == Aggregation.AVG) { @@ -223,7 +244,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { return Optional.empty(); } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { - if(aggregation == Aggregation.AVG || aggResult.hasDouble) { + if (aggregation == Aggregation.AVG || aggResult.hasDouble) { double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); } else { @@ -235,15 +256,17 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct private Optional processMinOrMaxResult(AggregationResult aggResult) { if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { - if(aggResult.hasDouble) { + if (aggResult.hasDouble) { double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); } else { return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); } - } else if (aggResult.dataType == DataType.STRING) { + } else if (aggResult.dataType == DataType.STRING) { return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); + } else if (aggResult.dataType == DataType.JSON) { + return Optional.of(new BasicTsKvEntry(ts, new JsonDataEntry(key, aggResult.jValue))); } else { return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue))); } @@ -253,6 +276,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct DataType dataType = null; Boolean bValue = null; String sValue = null; + String jValue = null; Double dValue = null; Long lValue = null; long count = 0; diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index d0ebb49572..8fc8b4ab8a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -29,6 +29,7 @@ import com.google.common.util.concurrent.FutureCallback; 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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -337,21 +339,31 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); break; case BOOLEAN: futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); break; case DOUBLE: futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); break; case STRING: futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON)); + break; + case JSON: + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); + futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); break; } } @@ -411,6 +423,13 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem .set(5, tsKvEntry.getStrValue().orElse(null), String.class) .set(6, tsKvEntry.getLongValue().orElse(null), Long.class) .set(7, tsKvEntry.getDoubleValue().orElse(null), Double.class); + Optional jsonV = tsKvEntry.getJsonValue(); + if (jsonV.isPresent()) { + stmt.setString(8, tsKvEntry.getJsonValue().get()); + } else { + stmt.setToNull(8); + } + return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); } @@ -669,7 +688,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem if (boolV != null) { kvEntry = new BooleanDataEntry(key, boolV); } else { - log.warn("All values in key-value row are nullable "); + String jsonV = row.get(ModelConstants.JSON_VALUE_COLUMN, String.class); + if (StringUtils.isNoneEmpty(jsonV)) { + kvEntry = new JsonDataEntry(key, jsonV); + } else { + log.warn("All values in key-value row are nullable "); + } } } } @@ -772,8 +796,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + - "," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + - " VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); + "," + ModelConstants.DOUBLE_VALUE_COLUMN + + "," + ModelConstants.JSON_VALUE_COLUMN + ")" + + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); } return latestInsertStmt; } @@ -812,7 +837,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," + - ModelConstants.DOUBLE_VALUE_COLUMN + " " + + ModelConstants.DOUBLE_VALUE_COLUMN + "," + + ModelConstants.JSON_VALUE_COLUMN + " " + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM + @@ -829,7 +855,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," + - ModelConstants.DOUBLE_VALUE_COLUMN + " " + + ModelConstants.DOUBLE_VALUE_COLUMN + "," + + ModelConstants.JSON_VALUE_COLUMN + " " + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM); @@ -847,6 +874,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem return ModelConstants.LONG_VALUE_COLUMN; case DOUBLE: return ModelConstants.DOUBLE_VALUE_COLUMN; + case JSON: + return ModelConstants.JSON_VALUE_COLUMN; default: throw new RuntimeException("Not implemented!"); } @@ -856,27 +885,23 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem switch (kvEntry.getDataType()) { case BOOLEAN: Optional booleanValue = kvEntry.getBooleanValue(); - if (booleanValue.isPresent()) { - stmt.setBool(column, booleanValue.get().booleanValue()); - } + booleanValue.ifPresent(b -> stmt.setBool(column, b)); break; case STRING: Optional stringValue = kvEntry.getStrValue(); - if (stringValue.isPresent()) { - stmt.setString(column, stringValue.get()); - } + stringValue.ifPresent(s -> stmt.setString(column, s)); break; case LONG: Optional longValue = kvEntry.getLongValue(); - if (longValue.isPresent()) { - stmt.setLong(column, longValue.get().longValue()); - } + longValue.ifPresent(l -> stmt.setLong(column, l)); break; case DOUBLE: Optional doubleValue = kvEntry.getDoubleValue(); - if (doubleValue.isPresent()) { - stmt.setDouble(column, doubleValue.get().doubleValue()); - } + doubleValue.ifPresent(d -> stmt.setDouble(column, d)); + break; + case JSON: + Optional jsonValue = kvEntry.getJsonValue(); + jsonValue.ifPresent(jsonObject -> stmt.setString(column, jsonObject)); break; } } diff --git a/dao/src/main/resources/cassandra/schema-entities.cql b/dao/src/main/resources/cassandra/schema-entities.cql index e9844f7b1c..de2b088cef 100644 --- a/dao/src/main/resources/cassandra/schema-entities.cql +++ b/dao/src/main/resources/cassandra/schema-entities.cql @@ -410,6 +410,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf ( str_v text, long_v bigint, dbl_v double, + json_v text, last_update_ts bigint, PRIMARY KEY ((entity_type, entity_id, attribute_type), attribute_key) ) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; diff --git a/dao/src/main/resources/cassandra/schema-ts.cql b/dao/src/main/resources/cassandra/schema-ts.cql index 338b420436..c0f4b74467 100644 --- a/dao/src/main/resources/cassandra/schema-ts.cql +++ b/dao/src/main/resources/cassandra/schema-ts.cql @@ -30,6 +30,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( str_v text, long_v bigint, dbl_v double, + json_v text, PRIMARY KEY (( entity_type, entity_id, key, partition ), ts) ); @@ -51,5 +52,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf ( str_v text, long_v bigint, dbl_v double, + json_v text, PRIMARY KEY (( entity_type, entity_id ), key) ) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql new file mode 100644 index 0000000000..758aaafb10 --- /dev/null +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -0,0 +1,251 @@ +-- +-- Copyright © 2016-2020 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. +-- + + +CREATE TABLE IF NOT EXISTS admin_settings ( + id varchar(31) NOT NULL CONSTRAINT admin_settings_pkey PRIMARY KEY, + json_value varchar, + key varchar(255) +); + +CREATE TABLE IF NOT EXISTS alarm ( + id varchar(31) NOT NULL CONSTRAINT alarm_pkey PRIMARY KEY, + ack_ts bigint, + clear_ts bigint, + additional_info varchar, + end_ts bigint, + originator_id varchar(31), + originator_type integer, + propagate boolean, + severity varchar(255), + start_ts bigint, + status varchar(255), + tenant_id varchar(31), + propagate_relation_types varchar, + type varchar(255) +); + +CREATE TABLE IF NOT EXISTS asset ( + id varchar(31) NOT NULL CONSTRAINT asset_pkey PRIMARY KEY, + additional_info varchar, + customer_id varchar(31), + name varchar(255), + label varchar(255), + search_text varchar(255), + tenant_id varchar(31), + type varchar(255), + CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name) +); + +CREATE TABLE IF NOT EXISTS audit_log ( + id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY, + tenant_id varchar(31), + customer_id varchar(31), + entity_id varchar(31), + entity_type varchar(255), + entity_name varchar(255), + user_id varchar(31), + user_name varchar(255), + action_type varchar(255), + action_data varchar(1000000), + action_status varchar(255), + action_failure_details varchar(1000000) +); + +CREATE TABLE IF NOT EXISTS attribute_kv ( + entity_type varchar(255), + entity_id varchar(31), + attribute_type varchar(255), + attribute_key varchar(255), + bool_v boolean, + str_v varchar(10000000), + long_v bigint, + dbl_v double precision, + json_v varchar(10000000), + last_update_ts bigint, + CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key) +); + +CREATE TABLE IF NOT EXISTS component_descriptor ( + id varchar(31) NOT NULL CONSTRAINT component_descriptor_pkey PRIMARY KEY, + actions varchar(255), + clazz varchar UNIQUE, + configuration_descriptor varchar, + name varchar(255), + scope varchar(255), + search_text varchar(255), + type varchar(255) +); + +CREATE TABLE IF NOT EXISTS customer ( + id varchar(31) NOT NULL CONSTRAINT customer_pkey PRIMARY KEY, + additional_info varchar, + address varchar, + address2 varchar, + city varchar(255), + country varchar(255), + email varchar(255), + phone varchar(255), + search_text varchar(255), + state varchar(255), + tenant_id varchar(31), + title varchar(255), + zip varchar(255) +); + +CREATE TABLE IF NOT EXISTS dashboard ( + id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, + configuration varchar(10000000), + assigned_customers varchar(1000000), + search_text varchar(255), + tenant_id varchar(31), + title varchar(255) +); + +CREATE TABLE IF NOT EXISTS device ( + id varchar(31) NOT NULL CONSTRAINT device_pkey PRIMARY KEY, + additional_info varchar, + customer_id varchar(31), + type varchar(255), + name varchar(255), + label varchar(255), + search_text varchar(255), + tenant_id varchar(31), + CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name) +); + +CREATE TABLE IF NOT EXISTS device_credentials ( + id varchar(31) NOT NULL CONSTRAINT device_credentials_pkey PRIMARY KEY, + credentials_id varchar, + credentials_type varchar(255), + credentials_value varchar, + device_id varchar(31), + CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id) +); + +CREATE TABLE IF NOT EXISTS event ( + id varchar(31) NOT NULL CONSTRAINT event_pkey PRIMARY KEY, + body varchar(10000000), + entity_id varchar(31), + entity_type varchar(255), + event_type varchar(255), + event_uid varchar(255), + tenant_id varchar(31), + CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid) +); + +CREATE TABLE IF NOT EXISTS relation ( + from_id varchar(31), + from_type varchar(255), + to_id varchar(31), + to_type varchar(255), + relation_type_group varchar(255), + relation_type varchar(255), + additional_info varchar, + CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type) +); + +CREATE TABLE IF NOT EXISTS tb_user ( + id varchar(31) NOT NULL CONSTRAINT tb_user_pkey PRIMARY KEY, + additional_info varchar, + authority varchar(255), + customer_id varchar(31), + email varchar(255) UNIQUE, + first_name varchar(255), + last_name varchar(255), + search_text varchar(255), + tenant_id varchar(31) +); + +CREATE TABLE IF NOT EXISTS tenant ( + id varchar(31) NOT NULL CONSTRAINT tenant_pkey PRIMARY KEY, + additional_info varchar, + address varchar, + address2 varchar, + city varchar(255), + country varchar(255), + email varchar(255), + phone varchar(255), + region varchar(255), + search_text varchar(255), + state varchar(255), + title varchar(255), + zip varchar(255) +); + +CREATE TABLE IF NOT EXISTS user_credentials ( + id varchar(31) NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY, + activate_token varchar(255) UNIQUE, + enabled boolean, + password varchar(255), + reset_token varchar(255) UNIQUE, + user_id varchar(31) UNIQUE +); + +CREATE TABLE IF NOT EXISTS widget_type ( + id varchar(31) NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY, + alias varchar(255), + bundle_alias varchar(255), + descriptor varchar(1000000), + name varchar(255), + tenant_id varchar(31) +); + +CREATE TABLE IF NOT EXISTS widgets_bundle ( + id varchar(31) NOT NULL CONSTRAINT widgets_bundle_pkey PRIMARY KEY, + alias varchar(255), + search_text varchar(255), + tenant_id varchar(31), + title varchar(255) +); + +CREATE TABLE IF NOT EXISTS rule_chain ( + id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY, + additional_info varchar, + configuration varchar(10000000), + name varchar(255), + first_rule_node_id varchar(31), + root boolean, + debug_mode boolean, + search_text varchar(255), + tenant_id varchar(31) +); + +CREATE TABLE IF NOT EXISTS rule_node ( + id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY, + rule_chain_id varchar(31), + additional_info varchar, + configuration varchar(10000000), + type varchar(255), + name varchar(255), + debug_mode boolean, + search_text varchar(255) +); + +CREATE TABLE IF NOT EXISTS entity_view ( + id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY, + entity_id varchar(31), + entity_type varchar(255), + tenant_id varchar(31), + customer_id varchar(31), + type varchar(255), + name varchar(255), + keys varchar(10000000), + start_ts bigint, + end_ts bigint, + search_text varchar(255), + additional_info varchar +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index f59b1045bc..55893fc124 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -74,6 +74,7 @@ CREATE TABLE IF NOT EXISTS attribute_kv ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v json, last_update_ts bigint, CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key) ); diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index e8cf0de263..7251d8be4e 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v json, CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) ); @@ -42,5 +43,6 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v json, CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-ts-hsql.sql b/dao/src/main/resources/sql/schema-ts-hsql.sql index c29d7e2ed7..eb053a7a84 100644 --- a/dao/src/main/resources/sql/schema-ts-hsql.sql +++ b/dao/src/main/resources/sql/schema-ts-hsql.sql @@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS ts_kv ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v varchar(10000000), CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) ); @@ -35,6 +36,7 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v varchar(10000000), CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 465c2d51e3..32b6762c8e 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -21,7 +21,8 @@ CREATE TABLE IF NOT EXISTS ts_kv ( bool_v boolean, str_v varchar(10000000), long_v bigint, - dbl_v double precision + dbl_v double precision, + json_v json ) PARTITION BY RANGE (ts); CREATE TABLE IF NOT EXISTS ts_kv_latest ( @@ -32,6 +33,7 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest ( str_v varchar(10000000), long_v bigint, dbl_v double precision, + json_v json, CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java index f4d11b328e..0af8603bf1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java @@ -30,7 +30,7 @@ public class JpaDaoTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"), "sql/drop-all-tables.sql", "sql-test.properties" ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index caddbabc35..7ebab237a8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite { @ClassRule public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), + Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), "sql/drop-all-tables.sql", "sql-test.properties" ); 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 4db628a7f4..be4e6b4e30 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 @@ -21,6 +21,7 @@ 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 com.google.gson.JsonParseException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.thingsboard.rule.engine.api.TbContext; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -77,7 +79,8 @@ public abstract class TbAbstractGetAttributesNode findEntityIdAsync(TbContext ctx, TbMsg msg); @@ -168,6 +171,12 @@ public abstract class TbAbstractGetAttributesNode Date: Sat, 15 Feb 2020 11:47:52 +0200 Subject: [PATCH 192/261] Introduced SMTP TLS version to default mail service and send email node * added tlsVersion to TbSendEmailNode * added tlsVersion to DefaultMailService * added check tlsVersion for old version --- .../DefaultSystemDataLoaderService.java | 5 +- .../service/mail/DefaultMailService.java | 10 +- .../main/resources/cassandra/system-data.cql | 3 +- dao/src/main/resources/sql/system-data.sql | 3 +- .../rule/engine/mail/TbSendEmailNode.java | 13 +- .../mail/TbSendEmailNodeConfiguration.java | 2 + .../app/admin/outgoing-mail-settings.tpl.html | 6 +- ui/src/app/locale/locale.constant-cs_CZ.json | 2 + ui/src/app/locale/locale.constant-de_DE.json | 2 + ui/src/app/locale/locale.constant-el_GR.json | 2 + ui/src/app/locale/locale.constant-en_US.json | 2 + ui/src/app/locale/locale.constant-es_ES.json | 2 + ui/src/app/locale/locale.constant-fa_IR.json | 2 + ui/src/app/locale/locale.constant-fr_FR.json | 3470 +++++++++-------- ui/src/app/locale/locale.constant-it_IT.json | 2 + ui/src/app/locale/locale.constant-ja_JA.json | 3056 +++++++-------- ui/src/app/locale/locale.constant-ko_KR.json | 2 + ui/src/app/locale/locale.constant-lv_LV.json | 2 + ui/src/app/locale/locale.constant-ru_RU.json | 2 + ui/src/app/locale/locale.constant-tr_TR.json | 2 + ui/src/app/locale/locale.constant-uk_UA.json | 2 + ui/src/app/locale/locale.constant-zh_CN.json | 2 + ui/src/app/locale/locale.constant-zh_TW.json | 2 + 23 files changed, 3324 insertions(+), 3272 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index beba984077..fd907714fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -106,9 +106,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { node.put("smtpHost", "localhost"); node.put("smtpPort", "25"); node.put("timeout", "10000"); - node.put("enableTls", "false"); + node.put("enableTls", false); node.put("username", ""); - node.put("password", ""); //NOSONAR, key used to identify password field (not password value itself) + node.put("password", ""); + node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself) mailSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 9916c0203a..9d2ae3beac 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -103,7 +103,11 @@ public class DefaultMailService implements MailService { javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText()))); - javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", jsonConfig.has("enableTls") ? jsonConfig.get("enableTls").asText() : "false"); + boolean enableTls = jsonConfig.has("enableTls") && jsonConfig.get("enableTls").booleanValue(); + javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls); + if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) { + javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); + } return javaMailProperties; } @@ -213,7 +217,7 @@ public class DefaultMailService implements MailService { } @Override - public void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException { + public void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException { String subject = messages.getMessage("account.lockout.subject", null, Locale.US); Map model = new HashMap(); @@ -244,7 +248,7 @@ public class DefaultMailService implements MailService { } private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation, - String encoding, Map model) throws VelocityException { + String encoding, Map model) throws VelocityException { StringWriter result = new StringWriter(); mergeTemplate(velocityEngine, templateLocation, encoding, model, result); diff --git a/dao/src/main/resources/cassandra/system-data.cql b/dao/src/main/resources/cassandra/system-data.cql index 2a30dc80f7..96446449f3 100644 --- a/dao/src/main/resources/cassandra/system-data.cql +++ b/dao/src/main/resources/cassandra/system-data.cql @@ -38,7 +38,8 @@ VALUES ( now ( ), 'mail', '{ "smtpHost": "localhost", "smtpPort": "25", "timeout": "10000", - "enableTls": "false", + "enableTls": false, + "tlsVersion": "TLSv1.2", "username": "", "password": "" }' ); \ No newline at end of file diff --git a/dao/src/main/resources/sql/system-data.sql b/dao/src/main/resources/sql/system-data.sql index 6fe28be73d..f261eb2c04 100644 --- a/dao/src/main/resources/sql/system-data.sql +++ b/dao/src/main/resources/sql/system-data.sql @@ -38,7 +38,8 @@ VALUES ( '1e746126eaaefa6a91992ebcb67fe33', 'mail', '{ "smtpHost": "localhost", "smtpPort": "25", "timeout": "10000", - "enableTls": "false", + "enableTls": false, + "tlsVersion": "TLSv1.2", "username": "", "password": "" }' ); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 0b044ec37c..1c41c2a124 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -20,8 +20,12 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; +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; @@ -137,10 +141,13 @@ public class TbSendEmailNode implements TbNode { String protocol = this.config.getSmtpProtocol(); javaMailProperties.put("mail.transport.protocol", protocol); javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost()); - javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort()+""); - javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout()+""); + javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort() + ""); + javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout() + ""); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername()))); javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString()); + if (this.config.isEnableTls() && StringUtils.isNoneEmpty(this.config.getTlsVersion())) { + javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", this.config.getTlsVersion()); + } return javaMailProperties; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java index e7982b6590..3150b6d7f3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java @@ -29,6 +29,7 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration { private String smtpProtocol; private int timeout; private boolean enableTls; + private String tlsVersion; @Override public TbSendEmailNodeConfiguration defaultConfiguration() { @@ -39,6 +40,7 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration { configuration.setSmtpPort(25); configuration.setTimeout(10000); configuration.setEnableTls(false); + configuration.setTlsVersion("TLSv1.2"); return configuration; } } diff --git a/ui/src/app/admin/outgoing-mail-settings.tpl.html b/ui/src/app/admin/outgoing-mail-settings.tpl.html index 1352fbe345..20f988f866 100644 --- a/ui/src/app/admin/outgoing-mail-settings.tpl.html +++ b/ui/src/app/admin/outgoing-mail-settings.tpl.html @@ -78,8 +78,12 @@
admin.timeout-invalid
- {{ 'admin.enable-tls' | translate }} + + + + diff --git a/ui/src/app/locale/locale.constant-cs_CZ.json b/ui/src/app/locale/locale.constant-cs_CZ.json index 8e8a6b8a2f..59d6fd5ece 100644 --- a/ui/src/app/locale/locale.constant-cs_CZ.json +++ b/ui/src/app/locale/locale.constant-cs_CZ.json @@ -83,6 +83,8 @@ "timeout-required": "Hodnota Časový limit je povinná.", "timeout-invalid": "Tohle nevypadá jako platný časový limit.", "enable-tls": "Povolit TLS", + "tls-version": "Verze TLS", + "enter-tls-version" : "Zadejte verzi TLS", "send-test-mail": "Odeslat testovací zprávu" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index d5f43f4ef4..7fb595a882 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -83,6 +83,8 @@ "timeout-required": "Wartezeit ist erforderlich.", "timeout-invalid": "Das ist keine gültige Wartezeit.", "enable-tls": "TLS aktivieren", + "tls-version" : "TLS-Version", + "enter-tls-version" : "Geben Sie die TLS-Version ein", "send-test-mail": "Test E-Mail senden", "security-settings": "Sicherheitseinstellungen", "password-policy": "Kennwortrichtlinie", diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index 9b9783a9ad..7669e705b9 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -88,6 +88,8 @@ "timeout-required": "Απαιτείται τιμή Timeout.", "timeout-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη τιμή timeout.", "enable-tls": "Ενεργοποίηση TLS", + "tls-version": "Έκδοση TLS", + "enter-tls-version" : "Εισαγάγετε την έκδοση TLS", "send-test-mail": "Αποστολή δοκιμαστικού μηνύματος", "use-system-mail-settings": "Χρήση των ρυθμίσεων διακομιστή αλληλογραφίας συστήματος", "mail-templates": "Πρότυπα αλληλογραφίας", diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 48bff7d6cd..cfadab8e1f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -86,6 +86,8 @@ "timeout-required": "Timeout is required.", "timeout-invalid": "That doesn't look like a valid timeout.", "enable-tls": "Enable TLS", + "tls-version": "TLS version", + "enter-tls-version" : "Enter TLS version", "send-test-mail": "Send test mail", "security-settings": "Security settings", "password-policy": "Password policy", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 75abc889b3..4b76cd7b70 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -85,6 +85,8 @@ "timeout-required": "Tiempo de espera es requerido.", "timeout-invalid": "Eso no parece un tiempo de espera válido.", "enable-tls": "Habilitar TLS", + "tls-version": "Versión TLS", + "enter-tls-version" : "Ingrese la versión de TLS", "send-test-mail": "Enviar correo de prueba", "password-policy": "Política de contraseñas", "security-settings": "Configuraciones de seguridad", diff --git a/ui/src/app/locale/locale.constant-fa_IR.json b/ui/src/app/locale/locale.constant-fa_IR.json index 34eee792b9..968769fe29 100644 --- a/ui/src/app/locale/locale.constant-fa_IR.json +++ b/ui/src/app/locale/locale.constant-fa_IR.json @@ -83,6 +83,8 @@ "timeout-required": ".مهلت مورد نياز است", "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد", "enable-tls": "TLS فعال سازي", + "tls-version": "نسخه TLS", + "enter-tls-version" : "نسخه TLS را وارد کنید", "send-test-mail": "ارسال پيام آزمايشي" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index f234afbd88..c12418652a 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -1,1734 +1,1736 @@ -{ - "access": { - "access-forbidden": "Accès interdit", - "access-forbidden-text": "Vous n'avez pas accès à cet emplacement!
Essayez de vous connecter avec un autre utilisateur si vous souhaitez toujours accéder à cet emplacement.", - "refresh-token-expired": "La session a expiré", - "refresh-token-failed": "Impossible de rafraîchir la session", - "unauthorized": "non autorisé", - "unauthorized-access": "accès non autorisé", - "unauthorized-access-text": "Vous devez vous connecter pour avoir accès à cette ressource!" - }, - "action": { - "activate": "Activer", - "add": "Ajouter", - "apply": "Appliquer", - "apply-changes": "Appliquer les modifications", - "assign": "Attribuer", - "back": "retour", - "cancel": "Annuler", - "clear-search": "Effacer la recherche", - "close": "Fermer", - "continue": "Continue", - "copy": "Copier", - "copy-reference": "Copier la référence", - "create": "Créer", - "decline-changes": "Refuser les modifications", - "delete": "Supprimer", - "discard-changes": "Annuler les modifications", - "drag": "Drag", - "edit": "Modifier", - "edit-mode": "Mode édition", - "enter-edit-mode": "Entrer en mode édition", - "export": "Exporter", - "import": "Importer", - "make-private": "Rendre privé", - "no": "Non", - "ok": "OK", - "paste": "coller", - "paste-reference": "Coller référence", - "refresh": "Rafraîchir", - "remove": "Supprimer", - "run": "Exécuter", - "save": "Enregistrer", - "saveAs": "Enregistrer sous", - "search": "Rechercher", - "share": "Partager", - "share-via": "Partager via {{provider}}", - "sign-in": "Connectez-vous!", - "suspend": "Suspendre", - "unassign": "Retirer", - "undo": "Annuler", - "update": "mise à jour", - "view": "Afficher", - "yes": "Oui" - }, - "admin": { - "base-url": "URL de base", - "base-url-required": "L'URL de base est requise.", - "enable-tls": "Activer TLS", - "general": "Général", - "general-settings": "Paramètres généraux", - "mail-from": "Mail de", - "mail-from-required": "Mail de est requis.", - "outgoing-mail": "courrier sortant", - "outgoing-mail-settings": "Paramètres de courrier sortant", - "send-test-mail": "Envoyer un mail de test", - "smtp-host": "Hôte SMTP", - "smtp-host-required": "L'hôte SMTP est requis.", - "smtp-port": "Port SMTP", - "smtp-port-invalid": "Cela ne ressemble pas à un port smtp valide.", - "smtp-port-required": "Vous devez fournir un port smtp.", - "smtp-protocol": "Protocole SMTP", - "system-settings": "Paramètres système", - "test-mail-sent": "Le courrier de test a été envoyé avec succés!", - "timeout-invalid": "Cela ne ressemble pas à un délai d'expiration valide.", - "timeout-msec": "Délai (msec)", - "timeout-required": "Le délai est requis.", - "security-settings": "Les paramètres de sécurité", - "password-policy": "Politique de mot de passe", - "minimum-password-length": "Longueur minimale du mot de passe", - "minimum-password-length-required": "La longueur minimale du mot de passe est requise", - "minimum-password-length-range": "La longueur minimale du mot de passe doit être comprise entre 5 et 50.", - "minimum-uppercase-letters": "Nombre minimum de lettres majuscules", - "minimum-uppercase-letters-range": "Le nombre minimum de lettres majuscules ne peut pas être négatif", - "minimum-lowercase-letters": "Nombre minimum de lettres minuscules", - "minimum-lowercase-letters-range": "Le nombre minimum de lettres minuscules ne peut pas être négatif", - "minimum-digits": "Nombre minimum de chiffres", - "minimum-digits-range": "Le nombre minimum de chiffres ne peut pas être négatif", - "minimum-special-characters": "Nombre minimum de caractères spéciaux", - "minimum-special-characters-range": "Le nombre minimum de caractères spéciaux ne peut pas être négatif", - "password-expiration-period-days": "Délai d'expiration du mot de passe en jours", - "password-expiration-period-days-range": "La période d'expiration du mot de passe en jours ne peut pas être négative", - "password-reuse-frequency-days": "Fréquence de réutilisation du mot de passe en jours", - "password-reuse-frequency-days-range": "La fréquence de réutilisation du mot de passe en jours ne peut être négative", - "general-policy": "Politique générale", - "max-failed-login-attempts": "Nombre maximal de tentatives de connexion infructueuses avant que le compte ne soit verrouillé", - "minimum-max-failed-login-attempts-range": "Le nombre maximal de tentatives de connexion ayant échoué ne peut pas être négatif", - "user-lockout-notification-email": "En cas de verrouillage du compte d'utilisateur, envoyez une notification par courrier électronique." - }, - "aggregation": { - "aggregation": "agrégation", - "avg": "Moyenne", - "count": "Compte", - "function": "Fonction d'agrégation de données", - "group-interval": "Intervalle de regroupement", - "limit": "Valeurs maximales", - "max": "Max", - "min": "Min", - "none": "Aucune", - "sum": "Somme" - }, - "alarm": { - "ack-time": "Heure d'acquittement", - "acknowledge": "Acquitter", - "aknowledge-alarm-text": "Êtes-vous sûr de vouloir reconnaître l'alarme?", - "aknowledge-alarm-title": "Reconnaître l'alarme", - "aknowledge-alarms-text": "Êtes-vous sûr de vouloir acquitter {count, plural, 1 {1 alarme} other {# alarmes}}?", - "aknowledge-alarms-title": "Acquitter {count, plural, 1 {1 alarme} other {# alarmes}}", - "alarm": "Alarme", - "alarm-details": "Détails de l'alarme", - "alarm-required": "Une alarme est requise", - "alarm-status": "État d'alarme", - "alarm-status-filter": "Filtre d'état d'alarme", - "alarms": "Alarmes", - "clear": "Effacer", - "clear-alarm-text": "Êtes-vous sûr de vouloir effacer l'alarme?", - "clear-alarm-title": "Effacer l'alarme", - "clear-alarms-text": "Êtes-vous sûr de vouloir effacer {count, plural, 1 {1 alarme} other {# alarmes}}?", - "clear-alarms-title": "Effacer {count, plural, 1 {1 alarme} other {# alarmes}}", - "clear-time": "Heure d'éffacement", - "created-time": "Heure de création", - "details": "Détails", - "display-status": { - "ACTIVE_ACK": "Active acquittée", - "ACTIVE_UNACK": "Active non acquittée", - "CLEARED_ACK": "effacée acquittée", - "CLEARED_UNACK": "effacée non acquittée" - }, - "end-time": "Heure de fin", - "min-polling-interval-message": "Un intervalle d'interrogation d'au moins 1 seconde est autorisé.", - "no-alarms-matching": "Aucune alarme correspondant à {{entity}} n'a été trouvée. ", - "no-alarms-prompt": "Aucune alarme", - "no-data": "Aucune donnée à afficher", - "originator": "Source", - "originator-type": "Type de Source", - "polling-interval": "Intervalle d'interrogation des alarmes (sec)", - "polling-interval-required": "L'intervalle d'interrogation des alarmes est requis.", - "search": "Rechercher des alarmes", - "search-status": { - "ACK": "acquitté", - "ACTIVE": "active", - "ANY": "Toutes", - "CLEARED": "effacée", - "UNACK": "non acquittée" - }, - "select-alarm": "Sélectionnez une alarme", - "selected-alarms": "{count, plural, 1 {1 alarme} other {# alarmes}} sélectionnées", - "severity": "Gravité", - "severity-critical": "Critique", - "severity-indeterminate": "indéterminée", - "severity-major": "Majeure", - "severity-minor": "mineure", - "severity-warning": "Avertissement", - "start-time": "Heure de début", - "status": "État", - "type": "Type" - }, - "alias": { - "add": "Ajouter un alias", - "all-entities": "Toutes les entités", - "any-relation": "toutes", - "default-entity-parameter-name": "Par défaut", - "default-state-entity": "Entité d'état par défaut", - "duplicate-alias": "Un alias portant le même nom existe déjà.", - "edit": "Modifier l'alias", - "entity-filter": "Filtre d'entité", - "entity-filter-no-entity-matched": "Aucune entité correspondant au filtre spécifié n'a été trouvée.", - "filter-type": "Type de filtre", - "filter-type-asset-search-query": "requête de recherche d'actifs", - "filter-type-asset-search-query-description": "Actifs de types {{assetTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-asset-type": "type d'actif", - "filter-type-asset-type-and-name-description": "Actifs de type '{{assetType}}' et dont le nom commence par '{{prefix}}'", - "filter-type-asset-type-description": "Actifs de type '{{assetType}}'", - "filter-type-device-search-query": "Requête de recherche de dispositif", - "filter-type-device-search-query-description": "Dispositifs de types {{deviceTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-device-type": "Type de dispositif", - "filter-type-device-type-and-name-description": "Dispositifs de type '{{deviceType}}' et dont le nom commence par '{{prefix}}'", - "filter-type-device-type-description": "Dispositifs de type '{{deviceType}}'", - "filter-type-entity-list": "Liste d'entités", - "filter-type-entity-name": "Nom d'entité", - "filter-type-entity-view-search-query": "Requête de recherche vue d'entité", - "filter-type-entity-view-search-query-description": "Vues d'entité avec les types {{entityViewTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-entity-view-type": "Type de vue d'entité", - "filter-type-entity-view-type-and-name-description": "Vues d'entité de type '{{entityView}}' et dont le nom commence par '{{prefix}}'", - "filter-type-entity-view-type-description": "Vues d'entité de type '{{entityView}}'", - "filter-type-relations-query": "Interrogation des relations", - "filter-type-relations-query-description": "{{entities}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-required": "Le type de filtre est requis.", - "filter-type-single-entity": "Entité unique", - "filter-type-state-entity": "Entité de l'état du tableau de bord", - "filter-type-state-entity-description": "Entité extraite des paramétres d'état du tableau de bord", - "max-relation-level": "Niveau de relation maximum", - "name": "Nom de l'alias", - "name-required": "Le nom d'alias est requis", - "no-entity-filter-specified": "Aucun filtre d'entité spécifié", - "resolve-multiple": "Résoudre en plusieurs entités", - "root-entity": "Entité racine", - "root-state-entity": "Utiliser l'entité d'état du tableau de bord en tant que racine", - "state-entity": "Entité d'état du tableau de bord", - "state-entity-parameter-name": "Nom du paramétre d'entité d'état", - "unlimited-level": "niveau illimité" - }, - "asset": { - "add": "Ajouter un actif", - "add-asset-text": "Ajouter un nouvel actif", - "any-asset": "Tout actif", - "asset": "Actif", - "asset-details": "Détails de l'actif", - "asset-file": "Actif file", - "asset-public": "L'actif est public", - "asset-required": "Actif requis", - "asset-type": "Type d'actif", - "asset-type-list-empty": "Aucun type d'actif sélectionné.", - "asset-type-required": "Le type d'actif est requis.", - "asset-types": "Types d'actif", - "assets": "Actifs", - "assign-asset-to-customer": "Attribuer des actifs au client", - "assign-asset-to-customer-text": "Veuillez sélectionner les actifs à attribuer au client", - "assign-assets": "Attribuer des actifs", - "assign-assets-text": "Attribuer {count, plural, 1 {1 asset} other {# assets}} au client", - "assign-new-asset": "Attribuer un nouvel Asset", - "assign-to-customer": "Attribuer au client", - "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les actifs", - "assignedToCustomer": "attribué au client", - "copyId": "Copier l'Id de l'actif", - "delete": "Supprimer un actif", - "delete-asset-text": "Faites attention, après la confirmation, l'actif et toutes les données associées deviendront irrécupérables.", - "delete-asset-title": "Êtes-vous sûr de vouloir supprimer l'actif '{{assetName}}'?", - "delete-assets": "Supprimer des actifs", - "delete-assets-action-title": "Supprimer {count, plural, 1 {1 asset} other {# assets}}", - "delete-assets-text": "Attention, après la confirmation, tous les actifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-assets-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 asset} other {# assets}}?", - "description": "Description", - "details": "Détails", - "enter-asset-type": "Entrez le type d'actif", - "events": "Evénements", - "idCopiedMessage": "L'Id d'asset a été copié dans le presse-papier", - "import": "Import actifs", - "make-private": "Rendre l'actif privé", - "make-private-asset-text": "Après la confirmation, l'actif et toutes ses données seront rendus privés et ne seront pas accessibles par d'autres.", - "make-private-asset-title": "Êtes-vous sûr de vouloir rendre l'actif '{{assetName}}' privé '?", - "make-public": "Rendre l'actif public", - "make-public-asset-text": "Après la confirmation, l'asset et toutes ses données seront rendus publics et accessibles aux autres.", - "make-public-asset-title": "Êtes-vous sûr de vouloir rendre l'actif '{{assetName}}' public '?", - "management": "Gestion d'actifs", - "name": "Nom", - "name-required": "Nom est requis.", - "name-starts-with": "Le nom de l'actif commence par", - "no-asset-types-matching": "Aucun type d'actif correspondant à {{entitySubtype}} n'a été trouvé. ", - "no-assets-matching": "Aucun actif correspondant à {{entity}} n'a été trouvé. ", - "no-assets-text": "Aucun actif trouvé", - "public": "Public", - "select-asset": "Sélectionner un actif", - "select-asset-type": "Sélectionner le type d'actif", - "type": "Type", - "type-required": "Le type est requis.", - "unassign-asset": "Retirer l'actif", - "unassign-asset-text": "Après la confirmation, l'actif sera non attribué et ne sera pas accessible au client.", - "unassign-asset-title": "Êtes-vous sûr de vouloir retirer l'attribution de l'actif '{{assetName}}'?", - "unassign-assets": "Retirer les actifs", - "unassign-assets-action-title": "Retirer {count, plural, 1 {1 asset} other {# assets}} du client", - "unassign-assets-text": "Après la confirmation, tous les actifs sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", - "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?", - "unassign-from-customer": "Retirer du client", - "view-assets": "Afficher les actifs", - "label": "Label" - }, - "attribute": { - "add": "Ajouter un attribut", - "add-to-dashboard": "Ajouter au tableau de bord", - "add-widget-to-dashboard": "Ajouter un widget au tableau de bord", - "attributes": "Attributs", - "attributes-scope": "Étendue des attributs d'entité", - "delete-attributes": "Supprimer les attributs", - "delete-attributes-text": "Attention, après la confirmation, tous les attributs sélectionnés seront supprimés.", - "delete-attributes-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 attribut} other {# attributs}}?", - "enter-attribute-value": "Entrez la valeur de l'attribut", - "key": "Clé", - "key-required": "La Clé d'attribut est requise.", - "last-update-time": "Dernière mise à jour", - "latest-telemetry": "Dernière télémétrie", - "next-widget": "Widget suivant", - "prev-widget": "Widget précédent", - "scope-client": "Attributs du client", - "scope-latest-telemetry": "Dernière télémétrie", - "scope-server": "Attributs du serveur", - "scope-shared": "Attributs partagés", - "selected-attributes": "{count, plural, 1 {1 attribut} other {# attributs}} sélectionnés", - "selected-telemetry": "{count, plural, 1 {1 unité de télémétrie} other {# unités de télémétrie}} sélectionnées", - "show-on-widget": "Afficher sur le widget", - "value": "Valeur", - "value-required": "La valeur d'attribut est obligatoire.", - "widget-mode": "Mode du widget" - }, - "audit-log": { - "action-data": "Action data", - "audit": "Audit", - "audit-log-details": "Détails du journal d'audit", - "audit-logs": "Journaux d'audit", - "clear-search": "Effacer la recherche", - "details": "Détails", - "entity-name": "Nom de l'entité", - "entity-type": "Type d'entité", - "failure-details": "Détails de l'échec", - "no-audit-logs-prompt": "Aucun journal trouvé", - "search": "Rechercher les journaux d'audit", - "status": "État", - "status-failure": "Échec", - "status-success": "Succès", - "timestamp": "Horodatage", - "type": "Type", - "type-activated": "Activé", - "type-added": "Ajouté", - "type-alarm-ack": "Acquitté", - "type-alarm-clear": "Effacé", - "type-assigned-to-customer": "Attribué au client", - "type-attributes-deleted": "Attributs supprimés", - "type-attributes-read": "Attributs lus", - "type-attributes-updated": "Attributs mis à jour", - "type-credentials-read": "Lecture des informations d'identification", - "type-credentials-updated": "Informations d'identification actualisées", - "type-deleted": "Supprimé", - "type-login": "Login", - "type-logout": "Connectez - Out", - "type-lockout": "Verrouillage", - "type-relation-add-or-update": "Relation mise à jour", - "type-relation-delete": "Relation supprimée", - "type-relations-delete": "Toutes les relations ont été supprimées", - "type-rpc-call": "Appel RPC", - "type-suspended": "Suspendu", - "type-unassigned-from-customer": "Non attribué du client", - "type-updated": "Mise à jour", - "user": "Utilisateur" - }, - "common": { - "enter-password": "Entrez le mot de passe", - "enter-search": "Entrez la recherche", - "enter-username": "Entrez le nom d'utilisateur", - "password": "Mot de passe", - "username": "Nom d'utilisateur" - }, - "confirm-on-exit": { - "html-message": "Vous avez des modifications non enregistrées.
Êtes-vous sûr de vouloir quitter cette page?", - "message": "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir quitter cette page?", - "title": "Modifications non enregistrées" - }, - "contact": { - "address": "Adresse", - "address2": "adresse 2", - "city": "Ville", - "country": "Pays", - "email": "Email", - "no-address": "Pas d'adresse", - "phone": "Téléphone", - "postal-code": "Code postal", - "postal-code-invalid": "Format de code postal / code postal invalide", - "state": "Province" - }, - "content-type": { - "binary": "Binaire (Base64)", - "json": "Json", - "text": "Texte" - }, - "custom": { - "widget-action": { - "action-cell-button": "Bouton de cellule d'action", - "marker-click": "Sur le marqueur cliquez", - "row-click": "Au rang, cliquez", - "polygon-click": "Cliquez sur le polygone", - "tooltip-tag-action": "Action de balise d'info-bulle", - "node-selected": "Sur le noeud sélectionné", - "element-click": "Sur l'élément HTML, cliquez sur", - "pie-slice-click": "Sur tranche cliquez", - "row-double-click": "Sur la ligne double clic" - } - }, - "customer": { - "add": "Ajouter un client", - "add-customer-text": "Ajouter un nouveau client", - "assets": "Actifs du client", - "copyId": "Copier l'id du client", - "customer": "Client", - "customer-details": "Détails du client", - "customer-required": "Le client est requis", - "customers": "Clients", - "dashboard": "Tableau de bord du client", - "dashboards": "tableaux de bord du client", - "default-customer": "Client par défaut", - "default-customer-required": "Le client par défaut est requis pour déboguer le tableau de bord au niveau du Tenant", - "delete": "Supprimer le client", - "delete-customer-text": "Faites attention, après la confirmation, le client et toutes les données associées deviendront irrécupérables.", - "delete-customer-title": "Êtes-vous sûr de vouloir supprimer le client '{{customerTitle}}'?", - "delete-customers-action-title": "Supprimer {count, plural, 1 {1 customer} other {# customers}}", - "delete-customers-text": "Faites attention, après la confirmation, tous les clients sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-customers-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 customer} other {# customers}}?", - "description": "Description", - "details": "Détails", - "devices": "Dispositifs du client", - "entity-views": "Vues de l'entité client", - "events": "Événements", - "idCopiedMessage": "L'Id du client a été copié dans le presse-papier", - "manage-assets": "Gérer les actifs", - "manage-customer-assets": "Gérer les actifs du client", - "manage-customer-dashboards": "Gérer les tableaux de bord du client", - "manage-customer-devices": "Gérer les dispositifs du client", - "manage-customer-users": "Gérer les utilisateurs du client", - "manage-dashboards": "Gérer les tableaux de bord", - "manage-devices": "Gérer les dispositifs", - "manage-public-assets": "Gérer les actifs publics", - "manage-public-dashboards": "Gérer les tableaux de bord publics", - "manage-public-devices": "Gérer les dispositifs publics", - "manage-users": "Gérer les utilisateurs", - "management": "Gestion des clients", - "no-customers-matching": "Aucun client correspondant à '{{entity}} n'a été trouvé.", - "no-customers-text": "Aucun client trouvé", - "public-assets": "Actifs publics", - "public-dashboards": "Tableaux de bord publics", - "public-devices": "Dispositifs publics", - "public-entity-views": "Vues d'entités publiques", - "select-customer": "Sélectionner un client", - "select-default-customer": "Sélectionnez le client par défaut", - "title": "Titre", - "title-required": "Le titre est requis." - }, - "dashboard": { - "add": "Ajouter un tableau de bord", - "add-dashboard-text": "Ajouter un nouveau tableau de bord", - "add-state": "Ajouter un état du tableau de bord", - "add-widget": "Ajouter un nouveau widget", - "alias-resolution-error-title": "Erreur de configuration des alias de tableau de bord", - "assign-dashboard-to-customer": "Attribuer des tableaux de bord au client", - "assign-dashboard-to-customer-text": "Veuillez sélectionner les tableaux de bord à affecter au client", - "assign-dashboards": "Attribuer des tableaux de bord", - "assign-dashboards-text": "Attribuer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} aux clients", - "assign-new-dashboard": "Attribuer un nouveau tableau de bord", - "assign-to-customer": "Attribuer au client", - "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les tableaux de bord", - "assign-to-customers": "Attribuer des tableaux de bord aux clients", - "assign-to-customers-text": "Veuillez sélectionner les clients pour attribuer les tableaux de bord", - "assigned-customers": "clients affectés", - "assignedToCustomer": "Attribué au client", - "assignedToCustomers": "attribué aux clients", - "autofill-height": "Hauteur de remplissage automatique", - "background-color": "Couleur de fond", - "background-image": "Image d'arriére-plan", - "background-size-mode": "Mode de taille d'arriére-plan", - "close-toolbar": "Fermer la barre d'outils", - "columns-count": "Nombre de colonnes", - "columns-count-required": "Le nombre de colonnes est requis.", - "configuration-error": "Erreur de configuration", - "copy-public-link": "Copier le lien public", - "create-new": "Créer un nouveau tableau de bord", - "create-new-dashboard": "Créer un nouveau tableau de bord", - "create-new-widget": "Créer un nouveau widget", - "dashboard": "Tableau de bord", - "dashboard-details": "Détails du tableau de bord", - "dashboard-file": "Fichier du tableau de bord", - "dashboard-import-missing-aliases-title": "Configurer les alias utilisés par le tableau de bord importé", - "dashboard-required": "Le tableau de bord est requis.", - "dashboards": "Tableaux de bord", - "delete": "Supprimer le tableau de bord", - "delete-dashboard-text": "Faites attention, après la confirmation, le tableau de bord et toutes les données associées deviendront irrécupérables.", - "delete-dashboard-title": "Êtes-vous sûr de vouloir supprimer le tableau de bord '{{dashboardTitle}}'?", - "delete-dashboards": "Supprimer les tableaux de bord", - "delete-dashboards-action-title": "Supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}", - "delete-dashboards-text": "Attention, après la confirmation, tous les tableaux de bord sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-dashboards-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", - "delete-state": "Supprimer l'état du tableau de bord", - "delete-state-text": "Etes-vous sûr de vouloir supprimer l'état du tableau de bord avec le nom '{{stateName}}'?", - "delete-state-title": "Supprimer l'état du tableau de bord", - "description": "Description", - "details": "Détails", - "display-dashboard-export": "Afficher l'exportation", - "display-dashboard-timewindow": "Afficher fenêtre de temps", - "display-dashboards-selection": "Afficher la sélection des tableaux de bord", - "display-entities-selection": "Afficher la sélection des entités", - "display-title": "Afficher le titre du tableau de bord", - "drop-image": "Déposer une image ou cliquez pour sélectionner un fichier à télécharger.", - "edit-state": "Modifier l'état du tableau de bord", - "export": "Exporter le tableau de bord", - "export-failed-error": "Impossible d'exporter le tableau de bord: {{error}}", - "hide-details": "Masquer les détails", - "horizontal-margin": "Marge horizontale", - "horizontal-margin-required": "Une valeur de marge horizontale est requise.", - "import": "Importer le tableau de bord", - "import-widget": "Importer un widget", - "invalid-aliases-config": "Impossible de trouver des dispositifs correspondant à certains filtres d'alias.
Veuillez contacter votre administrateur pour résoudre ce problème.", - "invalid-dashboard-file-error": "Impossible d'importer le tableau de bord: structure de données du tableau de bord non valide", - "invalid-widget-file-error": "Impossible d'importer le widget: structure de données de widget invalide.", - "is-root-state": "État racine", - "make-private": "Rendre privé le tableau de bord", - "make-private-dashboard": "Rendre privé le tableau de bord", - "make-private-dashboard-text": "Après la confirmation, le tableau de bord sera rendu privé et ne sera plus accessible aux autres.", - "make-private-dashboard-title": "Êtes-vous sûr de vouloir rendre le tableau de bord '{{dashboardTitle}}' privé?", - "make-public": "Rendre public le tableau de bord", - "manage-assigned-customers": "Gérer les clients affectés", - "manage-states": "Gérer les états du tableau de bord", - "management": "Gestion du tableau de bord", - "max-columns-count-message": "Seulement 1000 colonnes maximum sont autorisées.", - "max-horizontal-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge horizontale maximale.", - "max-mobile-row-height-message": "Seuls 200 pixels sont autorisés en tant que valeur maximale de hauteur de ligne mobile.", - "max-vertical-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge verticale maximale.", - "min-columns-count-message": "Seul un nombre minimum de 10 colonnes est autorisé.", - "min-horizontal-margin-message": "Seul 0 est autorisé comme valeur de marge horizontale minimale.", - "min-mobile-row-height-message": "Seuls 5 pixels sont autorisés en tant que valeur minimale de hauteur de ligne mobile.", - "min-vertical-margin-message": "Seul 0 est autorisé comme valeur de marge verticale minimale.", - "mobile-layout": "Paramètres de mise en page mobiles", - "mobile-row-height": "Hauteur de ligne mobile, px", - "mobile-row-height-required": "Une valeur de hauteur de ligne mobile est requise.", - "new-dashboard-title": "Nouveau titre du tableau de bord", - "no-dashboards-matching": "Aucun tableau de bord correspondant à {{entity}} n'a été trouvé. ", - "no-dashboards-text": "Aucun tableau de bord trouvé", - "no-image": "Aucune image sélectionnée", - "no-widgets": "Aucun widget configuré", - "open-dashboard": "Ouvrir le tableau de bord", - "open-toolbar": "Ouvrir la barre d'outils du tableau de bord", - "public": "Public", - "public-dashboard-notice": " Remarque: N'oubliez pas de rendre publics les dispositifs associés pour accéder à leurs données.", - "public-dashboard-text": "Votre tableau de bord {{dashboardTitle}} est maintenant public et accessible via le lien public : ", - "public-dashboard-title": "Le tableau de bord est maintenant public", - "public-link": "Lien public", - "public-link-copied-message": "Le lien public du tableau de bord a été copié dans le presse-papier", - "search-states": "Recherche des états du tableau de bord", - "select-dashboard": "Sélectionner le tableau de bord", - "select-devices": "Selectionner les dispositifs", - "select-existing": "Sélectionnez un tableau de bord existant", - "select-state": "Sélectionnez l'état cible", - "select-widget-subtitle": "Liste des types de widgets disponibles", - "select-widget-title": "Sélectionner un widget", - "selected-states": "{count, plural, 1 {1 état du tableau de bord} other {# états du tableau de bord}} sélectionnés", - "set-background": "Définir l'arrière-plan", - "settings": "Paramètres", - "show-details": "Afficher les détails", - "socialshare-text": "'{{dashboardTitle}}' propulsé par ThingsBoard", - "socialshare-title": "'{{dashboardTitle}}' propulsé par ThingsBoard", - "state": "État du tableau de bord", - "state-controller": "Contrôleur d'état", - "state-id": "ID d'état", - "state-id-exists": "L'état du tableau de bord avec le même Id existe déjà.", - "state-id-required": "L'Id d'état du tableau de bord est requis.", - "state-name": "Nom", - "state-name-required": "Le nom de l'état du tableau de bord est requis", - "states": "États du tableau de bord", - "title": "Titre", - "title-color": "Couleur du titre", - "title-required": "Le titre est requis.", - "toolbar-always-open": "Garder la barre d'outils ouverte", - "unassign-dashboard": "Retirer le tableau de bord", - "unassign-dashboard-text": "Après la confirmation, le tableau de bord ne sera pas attribué et ne sera pas accessible au client.", - "unassign-dashboard-title": "Êtes-vous sûr de vouloir annuler l'affectation du tableau de bord '{{dashboardTitle}}'?", - "unassign-dashboards": "Retirer les tableaux de bord", - "unassign-dashboards-action-text": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} des clients", - "unassign-dashboards-action-title": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} du client", - "unassign-dashboards-text": "Après la confirmation, tous les tableaux de bord sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", - "unassign-dashboards-title": "Etes-vous sûr de vouloir annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", - "unassign-from-customer": "Retirer du client", - "unassign-from-customers": "Retirer les tableaux de bord des clients", - "unassign-from-customers-text": "Veuillez sélectionner les clients à annuler l'affectation du ou des tableaux de bord", - "vertical-margin": "Marge verticale", - "vertical-margin-required": "Une valeur de marge verticale est requise", - "view-dashboards": "Afficher les tableaux de bord", - "widget-file": "Fichier du Widget", - "widget-import-missing-aliases-title": "Configurer les alias utilisés par le widget importé", - "widgets-margins": "Marge entre les widgets" - }, - "datakey": { - "advanced": "Avancé", - "alarm": "Champs d'alarme", - "alarm-fields-required": "Les champs d'alarme sont obligatoires.", - "attributes": "Attributs", - "color": "Couleur", - "configuration": "Configuration de la clé de données", - "data-generation-func": "Fonction de génération de données", - "decimals": "Nombre de chiffres après virgule flottante", - "function-types": "Types de fonctions", - "function-types-required": "Les types de fonctions sont obligatoires", - "label": "Label", - "maximum-function-types": "Maximum {count, plural, 1 {1 type de fonction est autorisé.} other {# types de fonctions sont autorisés}}", - "maximum-timeseries-or-attributes": "Maximum {count, plural, 1 {1 timeseries / attribut est autorisé.} other {# timeseries / attributs sont autorisés}}", - "prev-orig-value-description": "valeur précédente d'origine;", - "prev-value-description": "résultat de l'appel de fonction précédent;", - "settings": "Paramètres", - "time-description": "horodatage de la valeur actuelle;", - "time-prev-description": "horodatage de la valeur précédente;", - "timeseries": "Timeseries", - "timeseries-or-attributes-required": "Les timeseries / attributs d'entité sont obligatoires.", - "timeseries-required": "Les Timeseries de l'entité sont obligatoires.", - "units": "Symbole spécial à afficher à côté de la valeur", - "use-data-post-processing-func": "Utiliser la fonction de post-traitement des données", - "value-description": "la valeur actuelle;" - }, - "datasource": { - "add-datasource-prompt": "Veuillez ajouter une source de données", - "name": "Nom", - "type": "Type de source de données" - }, - "datetime": { - "date-from": "Date de", - "date-to": "Date à", - "time-from": "Heure de", - "time-to": "Heure à" - }, - "details": { - "edit-mode": "Mode édition", - "toggle-edit-mode": "Activer le mode édition" - }, - "device": { - "access-token": "Jeton d'accès", - "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 20 caractéres.", - "access-token-required": "Le jeton d'accès est requis.", - "accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier", - "add": "Ajouter un dispositif", - "add-alias": "Ajouter un alias de dispositif", - "add-device-text": "Ajouter un nouveau dispositif", - "alias": "Alias", - "alias-required": "Un alias du dispositif est requis.", - "aliases": "Alias des dispositifs", - "any-device": "N'importe quel dispositif", - "assign-device-to-customer": "Affecter des dispositifs au client", - "assign-device-to-customer-text": "Veuillez sélectionner les dispositif à affecter au client", - "assign-devices": "Attribuer des dispositifs", - "assign-devices-text": "Attribuer {count, plural, 1 {1 dispositif} other {# dispositifs}} au client", - "assign-new-device": "Attribuer un nouveau dispositif", - "assign-to-customer": "Attribuer au client", - "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les dispositifs", - "assignedToCustomer": "Attribué au client", - "configure-alias": "Configurer '{{alias}}' alias", - "copyAccessToken": "Copier le jeton d'accès", - "copyId": "Copier l'Id du dispositif", - "create-new-alias": "Créez un nouveau!", - "create-new-key": "Créez un nouveau!", - "credentials": "Informations d'identification", - "credentials-type": "Type d'identification", - "delete": "Supprimer le dispositif", - "delete-device-text": "Faites attention, après la confirmation, le dispositif et toutes les données associées deviendront irrécupérables.", - "delete-device-title": "Êtes-vous sûr de vouloir supprimer le dispositif '{{deviceName}}'?", - "delete-devices": "Supprimer les dispositifs", - "delete-devices-action-title": "Supprimer {count, plural, 1 {1 device} other {# devices}}", - "delete-devices-text": "Faites attention, après la confirmation, tous les dispositifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-devices-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 device} other {# devices}}?", - "description": "Description", - "details": "Détails", - "device": "Dispositif", - "device-alias": "Alias ​​du dispositif", - "device-credentials": "Informations d'identification du dispositif", - "device-details": "Détails du dispositif", - "device-list": "Liste des dispositifs", - "device-list-empty": "Aucun dispositif sélectionné.", - "device-name-filter-no-device-matched": "Aucun dispositif commençant par '{{device}} n'a été trouvé.", - "device-name-filter-required": "Le filtre de nom de dispositif est requis.", - "device-public": "Le dispositif est public", - "device-required": "Le dispositif est requis.", - "device-type": "Type de dispositif", - "device-type-list-empty": "Aucun type de dispositif sélectionné.", - "device-type-required": "Le type de dispositif est requis.", - "device-types": "Types de dispositif", - "devices": "Dispositifs", - "duplicate-alias-error": "Alias ??en double trouvé '{{alias}}'.
Les alias de dispositifs doivent être uniques dans le tableau de bord.", - "enter-device-type": "Entrez le type de dispositif", - "events": "Événements", - "idCopiedMessage": "l'Id du dispositif a été copié dans le presse-papiers", - "is-gateway": "Est une passerelle", - "label": "Label", - "make-private": "Rendre le dispositif privé", - "make-private-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendues privées et ne seront pas accessibles par d'autres.", - "make-private-device-title": "Êtes-vous sûr de vouloir rendre le dispositif {{deviceName}} privé?", - "make-public": "Rendre le dispositif public", - "make-public-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendus publics et accessibles par d'autres.", - "make-public-device-title": "Êtes-vous sûr de vouloir rendre le dispositif {{deviceName}} 'public?", - "manage-credentials": "Gérer les informations d'identification", - "management": "Gestion des dispositifs", - "name": "Nom", - "name-required": "Le nom est requis.", - "name-starts-with": "Le nom du dispositif commence par", - "no-alias-matching": "'{{alias}}' introuvable.", - "no-aliases-found": "Aucun alias trouvé.", - "no-device-types-matching": "Aucun type de dispositif correspondant à {{entitySubtype}} n'a été trouvé.", - "no-devices-matching": "Aucun dispositif correspondant à '{{entity}} n'a été trouvé.", - "no-devices-text": "Aucun dispositif trouvé", - "no-key-matching": "'{{key}}' introuvable.", - "no-keys-found": "Aucune clé trouvée", - "public": "Public", - "remove-alias": "Supprimer l'alias du dispositif", - "rsa-key": "Clé publique RSA", - "rsa-key-required": "La clé publique RSA est requise.", - "secret": "Secret", - "secret-required": "Code secret est requis.", - "select-device": "Selectionner un dispositif", - "select-device-type": "Sélectionner le type d'appareil", - "unable-delete-device-alias-text": "L'alias du dispositif '{{deviceAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", - "unable-delete-device-alias-title": "Impossible de supprimer l'alias du dispositif", - "unassign-device": "Annuler l'affectation du dispositif", - "unassign-device-text": "Après la confirmation, le dispositif ne sera pas attribué et ne sera pas accessible au client.", - "unassign-device-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{deviceName}} '?", - "unassign-devices": "Annuler l'affectation des dispositifs", - "unassign-devices-action-title": "Annuler l'affectation de {count, plural, 1 {1 device} other {#devices}} du client", - "unassign-devices-text": "Après la confirmation, tous les dispositifs sélectionnés ne seront pas attribues et ne seront pas accessibles par le client.", - "unassign-devices-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 device} other {# devices}}?", - "unassign-from-customer": "Retirer du client", - "use-device-name-filter": "Utiliser le filtre", - "view-credentials": "Afficher les informations d'identification", - "view-devices": "Afficher les dispositifs" - }, - "dialog": { - "close": "Fermer le dialogue" - }, - "entity": { - "add-alias": "Ajouter un alias d'entité", - "alarm-name-starts-with": "Les actifs dont le nom commence par '{{prefix}}'", - "alias": "Alias", - "alias-required": "Un alias d'entité est requis.", - "aliases": "alias d'entité", - "all-subtypes": "Tout", - "any-entity": "Toute entité", - "asset-name-starts-with": "Les Assets dont le nom commence par '{{prefix}}'", - "columns-to-display": "Colonnes à afficher", - "configure-alias": "Configurer '{{alias}}' alias", - "create-new-alias": "Créez un nouveau!", - "create-new-key": "Créez un nouveau!", - "customer-name-starts-with": "Les clients dont les noms commencent par '{{prefix}}'", - "dashboard-name-starts-with": "Les tableaux de bord dont les noms commencent par '{{prefix}}'", - "details": "Détails de l'entité", - "device-name-starts-with": "Dispositifs dont le nom commence par '{{prefix}}'", - "duplicate-alias-error": "Alias ​​en double trouvé '{{alias}}'.
Les alias d'entité doivent être uniques dans le tableau de bord.", - "enter-entity-type": "Entrez le type d'entité", - "entities": "Entités", - "entity": "Entité", - "entity-alias": "Alias de l'entité", - "entity-list": "Liste d'entités", - "entity-list-empty": "Aucune entité sélectionnée.", - "entity-name": "Nom de l'entité", - "entity-name-filter-no-entity-matched": "Aucune entité commençant par '{{entity}}' n'a été trouvée.", - "entity-name-filter-required": "Le filtre de nom d'entité est requis.", - "entity-type": "Type d'entité", - "entity-type-list": "Liste de types d'entités", - "entity-type-list-empty": "Aucun type d'entité sélectionné.", - "entity-types": "Types d'entité", - "entity-view-name-starts-with": "Les vues d'entité dont le nom commence par '{{prefix}}'", - "key": "Clé", - "key-name": "Nom de la clé", - "list-of-alarms": "{count, plural, 1 {Une alarme} other {Liste de # alarmes}}", - "list-of-assets": "{count, plural, 1 {Un Asset} other {Liste de # Assets}}", - "list-of-customers": "{count, plural, 1 {Un client} other {Liste de # clients}}", - "list-of-dashboards": "{count, plural, 1 {Un tableau de bord} other {Liste de # tableaux de bord}}", - "list-of-devices": "{count, plural, 1 {Un dispositif} other {Liste de # dispositifs}}", - "list-of-plugins": "{count, plural, 1 {Un plugin} other {Liste de # plugins}}", - "list-of-rulechains": "{count, plural, 1 {Une chaîne de règles} other {Liste de # chaînes de règles}}", - "list-of-rulenodes": "{count, plural, 1 {Un noeud de règles} other {Liste de # noeuds de règles}}", - "list-of-rules": "{count, plural, 1 {Une règle} other {Liste de # règles}}", - "list-of-tenants": "{count, plural, 1 {Un tenant} other {Liste de # tenants}}", - "list-of-users": "{count, plural, 1 {Un utilisateur} other {Liste de # utilisateurs}}", - "missing-entity-filter-error": "Le filtre est manquant pour l'alias '{{alias}}'.", - "name-starts-with": "Nom commence par", - "no-alias-matching": "'{{alias}}' introuvable.", - "no-aliases-found": "Aucun alias trouvé.", - "no-data": "Aucune donnée à afficher", - "no-entities-matching": "Aucune entité correspondant à '{{entity}}' n'a été trouvée.", - "no-entities-prompt": "Aucune entité trouvée", - "no-entity-types-matching": "Aucun type d'entité correspondant à {{entityType}} n'a été trouvé. ", - "no-key-matching": "'{{key}}' introuvable.", - "no-keys-found": "Aucune clé trouvée", - "plugin-name-starts-with": "Plugins dont les noms commencent par '{{prefix}}'", - "remove-alias": "Supprimer l'alias d'entité", - "rule-name-starts-with": "Régles dont les noms commencent par '{{prefix}}'", - "rulechain-name-starts-with": "Chaînes de régles dont les noms commencent par '{{prefix}}'", - "rulenode-name-starts-with": "Les noeuds de régles dont le nom commence par '{{prefix}}'", - "search": "Recherche d'entités", - "select-entities": "Sélectionner des entités", - "selected-entities": "{count, plural, 1 {1 entité} other {# entités}} sélectionnées", - "tenant-name-starts-with": "Les Tenant dont le nom commence par '{{prefix}}'", - "type": "Type", - "type-alarm": "Alarme", - "type-alarms": "Alarmes", - "type-asset": "Actif", - "type-assets": "Actifs", - "type-current-customer": "Client actuel", - "type-customer": "Client", - "type-customers": "Clients", - "type-dashboard": "Tableau de bord", - "type-dashboards": "Tableaux de bord", - "type-device": "Dispositif", - "type-devices": "Dispositifs", - "type-entity-view": "Vue d'entité", - "type-entity-views": "Vues d'entités", - "type-plugin": "Plugin", - "type-plugins": "Plugins", - "type-required": "Le type d'entité est obligatoire.", - "type-rule": "Régle", - "type-rulechain": "Chaîne de régles", - "type-rulechains": "Chaînes de régles", - "type-rulenode": "Noeud de régle", - "type-rulenodes": "Noeuds de régle", - "type-rules": "Régles", - "type-tenant": "Tenant", - "type-tenants": "Tenants", - "type-user": "Utilisateur", - "type-users": "Utilisateurs", - "unable-delete-entity-alias-text": "L'alias d'entité '{{entityAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", - "unable-delete-entity-alias-title": "Impossible de supprimer l'alias d'entité", - "use-entity-name-filter": "Utiliser un filtre", - "user-name-starts-with": "Utilisateurs dont les noms commencent par '{{prefix}}'" - }, - "entity-field": { - "address": "Adresse", - "address2": "Adresse 2", - "city": "Ville", - "country": "Pays", - "created-time": "Heure de création", - "email": "Email", - "first-name": "Prénom", - "last-name": "Nom de famille", - "name": "Nom", - "phone": "Téléphone", - "state": "Prov", - "title": "Titre", - "type": "Type", - "zip": "Code postal" - }, - "entity-view": { - "add": "Ajouter une vue d'entité", - "add-alias": "Ajouter un alias de vue d'entité", - "add-entity-view-text": "Ajouter une nouvelle vue d'entité", - "alias": "Alias", - "alias-required": "Un alias de vue d'entité est requis.", - "aliases": "Alias de vue d'entité", - "any-entity-view": "Toute vue d'entité", - "assign-entity-view-to-customer": "Attribuer une (des) vue (s) d'entité à un client", - "assign-entity-view-to-customer-text": "Veuillez sélectionner les vues d'entité à affecter au client", - "assign-entity-views": "Attribuer des vues d'entité", - "assign-entity-views-text": "Attribuer { count, plural, 1 {1 entityView} other {# entityViews} } au client", - "assign-new-entity-view": "Attribuer une nouvelle vue d'entité", - "assign-to-customer": "Attribuer au client", - "assign-to-customer-text": "Veuillez sélectionner le client auquel attribuer la ou les vues d'entité.", - "assignedToCustomer": "Assigné au client", - "attributes-propagation": "Propagation des attributs", - "attributes-propagation-hint": "La vue d'entité copiera automatiquement les attributs spécifiés de l'entité cible chaque fois que vous enregistrez ou mettez à jour cette vue d'entité. Pour des raisons de performances, les attributs d'entité cible ne sont pas propagés à la vue d'entité à chaque changement d'attribut. Vous pouvez activer la propagation automatique en configurant le noeud de règle \" copier pour afficher \" dans votre chaîne de règles et en liant les messages \"Post attributs \" et \"attributs mis à jour \" au nouveau noeud de règle.", - "client-attributes": "Attributs du client", - "client-attributes-placeholder": "Attributs du client", - "configure-alias": "Configurez l'alias '{{alias}}'", - "copyId": "Copier l'ID de la vue d'entité", - "create-new-alias": "Créer un nouveau!", - "create-new-key": "Créer un nouveau!", - "date-limits": "Limites de date", - "delete": "Supprimer la vue d'entité", - "delete-entity-view-text": "Attention, après la confirmation, la vue de l'entité et toutes les données associées deviendront irrécupérables.", - "delete-entity-view-title": "Êtes-vous sûr de vouloir supprimer la vue de l'entité '{{entityViewName}}'?", - "delete-entity-views": "Supprimer les vues d'entité", - "delete-entity-views-action-title": "Supprimer { count, plural, 1 {1 entityView} other {# entityViews} }", - "delete-entity-views-text": "Attention, après la confirmation, toutes les vues d'entité sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", - "delete-entity-views-title": "Êtes-vous sûr de vouloir voir l'entité { count, plural, 1 {1 entityView} other {# entityViews} }?", - "description": "Description", - "details": "Détails", - "duplicate-alias-error": "Alias '{{alias}}' existe déjà.
Les alias de vue d'entité doivent être uniques dans le tableau de bord.", - "end-date": "Date de fin", - "end-ts": "Heure de fin", - "enter-entity-view-type": "Entrer le type de vue d'entité", - "entity-view": "Vue d'entité", - "entity-view-alias": "Alias de vue d'entité", - "entity-view-details": "Détails de la vue d'entité", - "entity-view-list": "Liste de vues d'entités", - "entity-view-list-empty": "Aucune vue d'entité sélectionnée.", - "entity-view-name-filter-no-entity-view-matched": "Aucune vue d'entité commençant par '{{entityView}}' n'a été trouvée.", - "entity-view-name-filter-required": "Un filtre de nom de vue d'entité est requis.", - "entity-view-required": "Une vue d'entité est requise.", - "entity-view-type": "Type de vue d'entité", - "entity-view-type-list-empty": "Aucun type de vue d'entité sélectionné.", - "entity-view-type-required": "Le type d'entité est requis.", - "entity-view-types": "Types de vues d'entité", - "entity-views": "Vues d'entité", - "events": "Événements", - "make-private": "Rendre la vue d'entité privée", - "make-private-entity-view-text": "Après la confirmation, la vue de l'entité et toutes ses données seront rendues privées et ne seront pas accessibles par d'autres", - "make-private-entity-view-title": "Êtes-vous sûr de vouloir rendre la vue d'entité '{{entityViewName}}' privée?", - "make-public": "Rendre la vue d'entité publique", - "make-public-entity-view-text": "Après la confirmation, la vue de l'entité et toutes ses données seront rendues publiques et accessibles à d'autres", - "make-public-entity-view-title": "Voulez-vous vraiment que la vue de l'entité '{{entityViewName}}' soit publique?", - "management": "Gestion de vue d'entité", - "name": "Nom", - "name-required": "Un nom est requis.", - "name-starts-with": "Le nom de la vue d'entité commence par", - "no-alias-matching": "'{{alias}}' non trouvé.", - "no-aliases-found": "Aucun alias trouvé.", - "no-entity-view-types-matching": "Aucun type de vue d'entité correspondant à '{{entitySubtype}}' n'a été trouvé.", - "no-entity-views-matching": "Aucune vue d'entité correspondant à '{{entity}}' n'a été trouvée.", - "no-entity-views-text": "Aucune vue d'entité trouvée.", - "no-key-matching": "'{{key}}' non trouvé.", - "no-keys-found": "Aucune clé trouvée.", - "remove-alias": "Supprimer un alias de vue d'entité", - "select-entity-view": "Sélectionner une vue d'entité", - "select-entity-view-type": "Sélectionner le type de vue d'entité", - "server-attributes": "Attributs du serveur", - "server-attributes-placeholder": "Attributs du serveur", - "shared-attributes": "Attributs partagés", - "shared-attributes-placeholder": "Attributs partagés", - "start-date": "Date de début", - "start-ts": "Heure de début", - "target-entity": "Entité cible", - "timeseries": "Séries chronologiques", - "timeseries-data": "Données de séries chronologiques", - "timeseries-data-hint": "Configurez les clés de données de séries chronologiques de l'entité cible qui seront accessibles à la vue de l'entité. Ces données temporelles sont en lecture seule.", - "timeseries-placeholder": "Séries chronologiques", - "unable-entity-view-device-alias-text": "L'alias de dispositif '{{entityViewAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", - "unable-entity-view-device-alias-title": "Impossible de supprimer l'alias de la vue d'entité.", - "unassign-entity-view": "Annuler l'affectation de la vue d'entité", - "unassign-entity-view-text": "Après la confirmation, la vue de l'entité sera non attribuée et ne sera pas accessible par le client.", - "unassign-entity-view-title": "Voulez-vous vraiment annuler l'attribution de la vue d'entité '{{entityViewName}}'?", - "unassign-entity-views": "Annuler l'attribution des vues d'entité", - "unassign-entity-views-action-title": "Annuler l'attribution { count, plural, 1 {1 entityView} other {# entityViews} } du client", - "unassign-entity-views-text": "Après la confirmation, toutes les vues des entités sélectionnées seront non attribuées et ne seront pas accessibles par le client.", - "unassign-entity-views-title": "Êtes-vous sûr de vouloir annuler l'attribution { count, plural, 1 {1 entityView} other {# entityViews} }?", - "unassign-from-customer": "Annuler l'attribution au client", - "use-entity-view-name-filter": "Use filter", - "view-entity-views": "Voir les vues d'entité" - }, - "error": { - "unable-to-connect": "Impossible de se connecter au serveur! Veuillez vérifier votre connexion Internet.", - "unhandled-error-code": "Code d'erreur non géré: {{errorCode}}", - "unknown-error": "Erreur inconnue" - }, - "event": { - "alarm": "Alarme", - "body": "Corps", - "data": "Données", - "data-type": "Type de données", - "entity": "Entité", - "error": "erreur", - "errors-occurred": "Des erreurs sont survenues", - "event": "événement", - "event-time": "Heure de l'événement", - "event-type": "Type d'événement", - "failed": "Échec", - "message-id": "Message Id", - "message-type": "Type de message", - "messages-processed": "Messages traités", - "metadata": "Métadonnées", - "method": "Méthode", - "no-events-prompt": "Aucun événement trouvé", - "relation-type": "Type de relation", - "server": "Serveur", - "status": "État", - "success": "Succès", - "type": "Type", - "type-debug-rule-chain": "Debug", - "type-debug-rule-node": "Debug", - "type-error": "Erreur", - "type-lc-event": "Evénement du cycle de vie", - "type-stats": "Statistiques" - }, - "extension": { - "add": "Ajouter une extension", - "add-attribute": "Ajouter un attribut", - "add-attribute-request": "Ajouter une demande d'attribut", - "add-attribute-update": "Ajouter une mise à jour d'attribut", - "add-broker": "Ajouter un Broker", - "add-config": "Ajouter une configuration de convertisseur", - "add-connect-request": "Ajouter une demande de connexion", - "add-converter": "Ajouter un convertisseur", - "add-device": "Ajouter un dispositif", - "add-disconnect-request": "Ajouter une demande de déconnexion", - "add-map": "Ajouter un élément de mappage", - "add-server-side-rpc-request": "Ajouter une requête RPC côté serveur", - "add-timeseries": "Ajouter des timeseries", - "anonymous": "Anonyme", - "attr-json-key-expression": "Expression json de la clé d'attribut", - "attr-topic-key-expression": "Expression du topic de la clé d'attribut", - "attribute-filter": "Filtre d'attribut", - "attribute-key-expression": "Expression de clé d'attribut", - "attribute-requests": "Demandes d'attributs", - "attribute-updates": "Mises à jour des attributs", - "attributes": "Attributs", - "basic": "Basic", - "brokers": "Brokers", - "ca-cert": "Fichier de certificat CA", - "cert": "Fichier de certificat *", - "client-scope": "Portée client", - "configuration": "Configuration", - "connect-requests": "Demandes de connexion", - "converter-configurations": "Configurations du convertisseur", - "converter-id": "ID du convertisseur", - "converter-json": "Json", - "converter-json-parse": "Impossible d'analyser le convertisseur json.", - "converter-json-required": "Le convertisseur json est requis.", - "converter-type": "Type de convertisseur", - "converters": "Convertisseurs", - "credentials": "Informations d'identification", - "custom": "Sur mesure", - "delete": "Supprimer l'extension", - "delete-extension-text": "Attention, après la confirmation, l'extension et toutes les données associées deviendront irrécupérables.", - "delete-extension-title": "Êtes-vous sûr de vouloir supprimer l'extension '{{extensionId}}'?", - "delete-extensions-text": "Attention, après la confirmation, toutes les extensions sélectionnées seront supprimées.", - "delete-extensions-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 extension} other {# extensions}}?", - "device-name-expression": "expression du nom du dispositif", - "device-name-filter": "Filtre de nom de dispositif", - "device-type-expression": "expression de type de dispositif", - "disconnect-requests": "Demandes de déconnection", - "drop-file": "Déposez un fichier ou cliquez pour sélectionner un fichier à télécharger.", - "edit": "Modifier l'extension", - "export-extension": "Exporter l'extension", - "export-extensions-configuration": "Exporter la configuration des extensions", - "extension-id": "Id de l'extension", - "extension-type": "Type d'extension", - "extensions": "Extensions", - "field-required": "Le champ est obligatoire", - "file": "Fichier d'extensions", - "filter-expression": "Expression du filtre", - "host": "Hôte", - "id": "Id", - "import-extension": "Importer une extension", - "import-extensions": "Importer des extensions", - "import-extensions-configuration": "Importer la configuration des extensions", - "invalid-file-error": "Fichier d'extension non valide", - "json-name-expression": "Expression json du nom du dispositif", - "json-parse": "Impossible d'analyser json transformer.", - "json-required": "Transformer json est requis.", - "json-type-expression": "Expression json du type de dispositif", - "key": "Clé", - "mapping": "Mappage", - "method-filter": "Filtre de méthode", - "modbus-add-server": "Ajouter serveur/esclave", - "modbus-add-server-prompt": "Veuillez ajouter serveur/esclave", - "modbus-attributes-poll-period": "Période d'interrogation des attributs (ms)", - "modbus-baudrate": "Débit en bauds", - "modbus-byte-order": "Ordre des octets", - "modbus-databits": "Bits de données", - "modbus-databits-range": "Les bits de données doivent être compris entre 7 et 8.", - "modbus-device-name": "Nom du dispositif", - "modbus-encoding": "Encodage", - "modbus-function": "Fonction", - "modbus-parity": "parité", - "modbus-poll-period": "Période d'interrogation (ms)", - "modbus-poll-period-range": "La période d'interrogation doit être une valeur positive.", - "modbus-port-name": "Nom du port série", - "modbus-register-address": "Adresse du registre", - "modbus-register-address-range": "L'adresse du registre doit être comprise entre 0 et 65535.", - "modbus-register-bit-index": "Bit index", - "modbus-register-bit-index-range": "L'index de bit doit être compris entre 0 et 15.", - "modbus-register-count": "Nombre de registre", - "modbus-register-count-range": "Le nombre de registres doit être une valeur positive.", - "modbus-server": "Serveurs / esclaves", - "modbus-stopbits": "Bits d'arrêt", - "modbus-stopbits-range": "Les bits d'arrêt doivent être compris entre 1 et 2.", - "modbus-tag": "Tag", - "modbus-timeseries-poll-period": "Période d'interrogation des Timeseries (ms)", - "modbus-transport": "Transport", - "modbus-unit-id": "Id de l'unité", - "modbus-unit-id-range": "L'ID de l'unité doit être compris entre 1 et 247.", - "no-file": "Aucun fichier sélectionné.", - "opc-add-server": "Ajouter un serveur", - "opc-add-server-prompt": "Veuillez ajouter un serveur", - "opc-application-name": "Nom de l'application", - "opc-application-uri": "Uri de l'application", - "opc-device-name-pattern": "modèle de nom du dispositif", - "opc-device-node-pattern": "modèle de noeud de dispositif", - "opc-identity": "Identité", - "opc-keystore": "Magasin de clés", - "opc-keystore-alias": "Alias", - "opc-keystore-key-password": "Mot de passe de la clé", - "opc-keystore-location": "Emplacement *", - "opc-keystore-password": "Mot de passe", - "opc-keystore-type": "Type", - "opc-scan-period-in-seconds": "Période d'analyse en secondes", - "opc-security": "Sécurité", - "opc-server": "Serveurs", - "opc-type": "Type", - "password": "Mot de passe", - "pem": "PEM", - "port": "Port", - "port-range": "Le port doit être compris entre 1 et 65535.", - "private-key": "Fichier de clé privée *", - "request-id-expression": "Expression de demande d'id", - "request-id-json-expression": "Expression json de la demande d'id", - "request-id-topic-expression": "Expression de la demande d'id du topic", - "request-topic-expression": "Expression de la demande du topic", - "response-timeout": "Délai de réponse en millisecondes", - "response-topic-expression": "Expression du topic de la réponse", - "retry-interval": "Intervalle de nouvelle tentative en millisecondes", - "selected-extensions": "{count, plural, 1 {1 extension} other {# extensions}} sélectionné", - "server-side-rpc": "RPC côté serveur", - "ssl": "Ssl", - "sync": { - "last-sync-time": "Dernière heure de synchronisation", - "not-available": "Non disponible", - "not-sync": "Non sync", - "status": "Status", - "sync": "Sync" - }, - "timeout": "Délai d'attente en millisecondes", - "timeseries": "Timeseries", - "to-double": "Au double", - "token": "Jeton de sécurité", - "topic": "Topic", - "topic-expression": "Expression du topic", - "topic-filter": "Filtre du topic", - "topic-name-expression": "Expression du nom du dispositif (topic)", - "topic-type-expression": "Expression de type de dispositif (topic)", - "transformer": "Transformer", - "transformer-json": "JSON *", - "type": "Type", - "unique-id-required": "L'identifiant d'extension actuel existe déjà.", - "username": "Nom d'utilisateur", - "value": "Valeur", - "value-expression": "Expression de la valeur" - }, - "fullscreen": { - "exit": "Quitter le plein écran", - "expand": "Afficher en plein écran", - "fullscreen": "Plein écran", - "toggle": "Activer le mode plein écran" - }, - "function": { - "function": "Fonction" - }, - "grid": { - "add-item-text": "Ajouter un nouvel élément", - "delete-item": "Supprimer l'élément", - "delete-item-text": "Faites attention, après la confirmation, cet élément et toutes les données associées deviendront irrécupérables.", - "delete-item-title": "Êtes-vous sûr de vouloir supprimer cet élément?", - "delete-items": "Supprimer les éléments", - "delete-items-action-title": "Supprimer {count, plural, 1 {1 élément} other {# éléments}}", - "delete-items-text": "Attention, après la confirmation, tous les éléments sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-items-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 élément} other {# éléments}}?", - "item-details": "Détails de l'élément", - "no-items-text": "Aucun élément trouvé", - "scroll-to-top": "Défiler vers le haut" - }, - "help": { - "goto-help-page": "Aller à la page d'aide" - }, - "home": { - "avatar": "Avatar", - "home": "Accueil", - "logout": "Déconnexion", - "menu": "Menu", - "open-user-menu": "Ouvrir le menu utilisateur", - "profile": "Profile" - }, - "icon": { - "icon": "Icône", - "material-icons": "Icônes matérielles", - "select-icon": "Sélectionner l'icône", - "show-all": "Afficher toutes les icônes" - }, - "import": { - "drop-file": "Déposez un fichier JSON ou cliquez pour sélectionner un fichier à télécharger.", - "no-file": "Aucun fichier sélectionné" - }, - "item": { - "selected": "Sélectionné" - }, - "js-func": { - "no-return-error": "La fonction doit renvoyer une valeur!", - "return-type-mismatch": "La fonction doit renvoyer une valeur de type '{{type}}' !", - "tidy": "Nettoyer" - }, - "key-val": { - "add-entry": "Ajouter une entrée", - "key": "Clé", - "no-data": "Aucune entrée", - "remove-entry": "Supprimer l'entrée", - "value": "Valeur" - }, - "language": { - "language": "Language", - "locales": { - "de_DE": "Allemand", - "en_US": "Anglais", - "fr_FR": "Français", - "es_ES": "Espagnol", - "it_IT": "Italien", - "ko_KR": "Coréen", - "ru_RU": "Russe", - "zh_CN": "Chinois", - "ja_JA": "Japonaise", - "tr_TR": "Turc", - "fa_IR": "Persane", - "uk_UA": "Ukrainien", - "cs_CZ": "Tchèque", - "el_GR": "Grec", - "lv_LV": "Letton" - } - }, - "layout": { - "color": "Couleur", - "layout": "Mise en page", - "main": "Principal", - "manage": "Gérer les mises en page", - "right": "Droite", - "select": "Sélectionner la mise en page cible", - "settings": "Paramètres de mise en page" - }, - "legend": { - "avg": "moy", - "max": "max", - "min": "min", - "position": "Position de la légende", - "settings": "Paramètres de la légende", - "show-avg": "Afficher la valeur moyenne", - "show-max": "Afficher la valeur maximale", - "show-min": "Afficher la valeur min", - "show-total": "Afficher la valeur totale", - "total": "total" - }, - "login": { - "create-password": "Créer un mot de passe", - "email": "Email", - "forgot-password": "Mot de passe oublié?", - "login": "Login", - "new-password": "Nouveau mot de passe", - "new-password-again": "nouveau mot de passe", - "password-again": "Mot de passe à nouveau", - "password-link-sent-message": "Le lien de réinitialisation du mot de passe a été envoyé avec succès!", - "password-reset": "Mot de passe réinitialisé", - "passwords-mismatch-error": "Les mots de passe saisis doivent être identiques!", - "remember-me": "Se souvenir de moi", - "request-password-reset": "Demander la réinitialisation du mot de passe", - "reset-password": "Réinitialiser le mot de passe", - "sign-in": "Veuillez vous connecter", - "username": "Nom d'utilisateur (courriel)" - }, - "position": { - "bottom": "Bas", - "left": "Gauche", - "right": "Droite", - "top": "Haut" - }, - "profile": { - "change-password": "Modifier le mot de passe", - "current-password": "Mot de passe actuel", - "last-login-time": "Dernière connexion", - "profile": "Profile" - }, - "relation": { - "add": "Ajouter une relation", - "add-relation-filter": "Ajouter un filtre de relation", - "additional-info": "Informations supplémentaires (JSON)", - "any-relation": "toute relation", - "any-relation-type": "N'importe quel type", - "delete": "Supprimer la relation", - "delete-from-relation-text": "Attention, après la confirmation, l'entité actuelle ne sera pas liée à l'entité '{{entityName}}'.", - "delete-from-relation-title": "Êtes-vous sûr de vouloir supprimer la relation de l'entité '{{entityName}}'?", - "delete-from-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et l'entité actuelle ne sera pas liée aux entités correspondantes.", - "delete-from-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", - "delete-to-relation-text": "Attention, après la confirmation, l'entité '{{entityName}} ne sera plus liée à l'entité actuelle.", - "delete-to-relation-title": "Êtes-vous sûr de vouloir supprimer la relation avec l'entité '{{entityName}}'?", - "delete-to-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et les entités correspondantes ne seront pas liées à l'entité en cours.", - "delete-to-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", - "direction": "Sens", - "direction-type": { - "FROM": "de", - "TO": "à" - }, - "edit": "Modifier la relation", - "from-entity": "De l'entité", - "from-entity-name": "Du nom d'entité", - "from-entity-type": "Du type d'entité", - "from-relations": "Relations sortantes", - "invalid-additional-info": "Impossible d'analyser les informations supplémentaires json.", - "relation-filters": "Filtres de relation", - "relation-type": "Type de relation", - "relation-type-required": "Le type de relation est requis.", - "relations": "Relations", - "remove-relation-filter": "Supprimer le filtre de relation", - "search-direction": { - "FROM": "De", - "TO": "Vers" - }, - "selected-relations": "{count, plural, 1 {1 relation} other {# relations}} sélectionné", - "to-entity": "Vers l'entité", - "to-entity-name": "vers le nom de l'entité", - "to-entity-type": "Vers le type d'entité", - "to-relations": "Relations entrantes", - "type": "Type" - }, - "rulechain": { - "add": "Ajouter une chaîne de règles", - "add-rulechain-text": "Ajouter une nouvelle chaîne de règles", - "copyId": "Copier l'identifiant de la chaîne de règles", - "create-new-rulechain": "Créer une nouvelle chaîne de règles", - "debug-mode": "Mode de débogage", - "delete": "Supprimer la chaîne de règles", - "delete-rulechain-text": "Attention, après la confirmation, la chaîne de règles et toutes les données associées deviendront irrécupérables.", - "delete-rulechain-title": "Voulez-vous vraiment supprimer la chaîne de règles '{{ruleChainName}}'?", - "delete-rulechains-action-title": "Supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}", - "delete-rulechains-text": "Attention, après la confirmation, toutes les chaînes de règles sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", - "delete-rulechains-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}?", - "description": "Description", - "details": "Détails", - "events": "Evénements", - "export": "Exporter la chaîne de règles", - "export-failed-error": "Impossible d'exporter la chaîne de règles: {{error}}", - "idCopiedMessage": "L'ID de la chaîne de règles a été copié dans le presse-papier", - "import": "Importer la chaîne de règles", - "invalid-rulechain-file-error": "Impossible d'importer la chaîne de règles: structure de données de la chaîne de règles non valide", - "management": "Gestion des règles", - "name": "Nom", - "name-required": "Le nom est requis.", - "no-rulechains-matching": "Aucune chaîne de règles correspondant à {{entity}} n'a été trouvée.", - "no-rulechains-text": "Aucune chaîne de règles trouvée", - "root": "Racine", - "rulechain": "Chaîne de règles", - "rulechain-details": "Détails de la chaîne de règles", - "rulechain-file": "Fichier de chaîne de règles", - "rulechain-required": "Chaîne de règles requise", - "rulechains": "Chaînes de règles", - "select-rulechain": "Sélectionner la chaîne de règles", - "set-root": "Rend la chaîne de règles racine (root) ", - "set-root-rulechain-text": "Après la confirmation, la chaîne de règles deviendra racine (root) et gérera tous les messages de transport entrants.", - "set-root-rulechain-title": "Voulez-vous vraiment que la chaîne de règles '{{ruleChainName}} soit racine (root) ?", - "system": "Système" - }, - "rulenode": { - "add": "Ajouter un noeud de règle", - "add-link": "Ajouter un lien", - "configuration": "Configuration", - "copy-selected": "Copier les éléments sélectionnés", - "create-new-link-label": "Créez un nouveau!", - "custom-link-label": "Etiquette de lien personnalisée", - "custom-link-label-required": "Une étiquette de lien personnalisée est requise", - "debug-mode": "Mode de débogage", - "delete": "Supprimer le noeud de règle", - "delete-selected": "Supprimer les éléments sélectionnés", - "delete-selected-objects": "Supprimer les nœuds et les connexions sélectionnés", - "description": "Description", - "deselect-all": "Désélectionner tout", - "deselect-all-objects": "Désélectionnez tous les nœuds et toutes les connexions", - "details": "Détails", - "directive-is-not-loaded": "La directive de configuration définie '{{directiveName}} n'est pas disponible.", - "events": "Événements", - "help": "Aide", - "invalid-target-rulechain": "Impossible de résoudre la chaîne de règles cible!", - "link": "Lien", - "link-details": "Détails du lien du noeud de la règle", - "link-label": "Étiquette du lien", - "link-label-required": "L'étiquette du lien est obligatoire", - "link-labels": "Étiquettes de lien", - "link-labels-required": "Les étiquettes de lien sont obligatoires", - "message": "Message", - "message-type": "Type de message", - "message-type-required": "Le type de message est obligatoire", - "metadata": "Métadonnées", - "metadata-required": "Les entrées de métadonnées ne peuvent pas être vides.", - "name": "Nom", - "name-required": "Le nom est requis.", - "no-link-label-matching": "'{{label}}' introuvable.", - "no-link-labels-found": "Aucune étiquette de lien trouvée", - "open-node-library": "Ouvrir la bibliothèque de noeud", - "output": "Output", - "rulenode-details": "Détails du noeud de la régle", - "search": "Recherche de noeuds", - "select-all": "Tout sélectionner", - "select-all-objects": "Sélectionnez tous les noeuds et connexions", - "select-message-type": "Sélectionner le type de message", - "test": "Test", - "test-script-function": "Tester le script", - "type": "Type", - "type-action": "Action", - "type-action-details": "Effectuer une action spéciale", - "type-enrichment": "Enrichissement", - "type-enrichment-details": "Ajouter des informations supplémentaires dans les métadonnées de message", - "type-external": "Externe", - "type-external-details": "Interagit avec le systéme externe", - "type-filter": "Filtre", - "type-filter-details": "Filtrer les messages entrants avec des conditions configurées", - "type-input": "Input", - "type-input-details": "Entrée logique de la chaîne de règles, transmet les messages entrants au prochain nœud de règle associé", - "type-rule-chain": "Chaîne de régles", - "type-rule-chain-details": "Transmet les messages entrants à la chaîne de régles spécifiée", - "type-transformation": "Transformation", - "type-transformation-details": "Modifier le payload du message et les métadonnées ", - "type-unknown": "Inconnu", - "type-unknown-details": "Noeud de règle non résolu", - "ui-resources-load-error": "Impossible de charger les ressources de configuration de l'interface utilisateur." - }, - "tenant": { - "add": "Ajouter un Tenant", - "add-tenant-text": "Ajouter un nouveau Tenant", - "admins": "Admins", - "copyId": "Copier l'Id du Tenant", - "delete": "Supprimer le Tenant", - "delete-tenant-text": "Attention, après la confirmation, le Tenant et toutes les données associées deviendront irrécupérables.", - "delete-tenant-title": "Êtes-vous sûr de vouloir supprimer le tenant '{{tenantTitle}}'?", - "delete-tenants-action-title": "Supprimer {count, plural, 1 {1 tenant} other {# tenants}}", - "delete-tenants-text": "Attention, après la confirmation, tous les Tenants sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-tenants-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 tenant} other {# tenants}}?", - "description": "Description", - "details": "Détails", - "events": "Événements", - "idCopiedMessage": "L'Id du Tenant a été copié dans le Presse-papiers", - "manage-tenant-admins": "Gérer les administrateurs du Tenant", - "management": "Gestion des Tenants", - "no-tenants-matching": "Aucun Tenant correspondant à {{entity}} n'a été trouvé. ", - "no-tenants-text": "Aucun Tenant trouvé", - "select-tenant": "Sélectionner un Tenant", - "tenant": "Tenant", - "tenant-details": "Détails du Tenant", - "tenant-required": "Tenant requis", - "tenants": "Tenants", - "title": "Titre", - "title-required": "Le titre est requis." - }, - "timeinterval": { - "advanced": "Avancé", - "days": "Jours", - "days-interval": "{days, plural, 1 {1 jour} other {# jours}}", - "hours": "Heures", - "hours-interval": "{hours, plural, 1 {1 heure} other {# heures}}", - "minutes": "Minutes", - "minutes-interval": "{minutes, plural, 1 {1 minute} other {# minutes}}", - "seconds": "Secondes", - "seconds-interval": "{seconds, plural, 1 {1 seconde} other {# secondes}}" - }, - "timewindow": { - "date-range": "Plage de dates", - "days": "{days, plural, 1 {jour} other {# jours}}", - "edit": "Modifier timewindow", - "history": "Historique", - "hours": "{hours, plural, 0 {heure} 1 {1 heure} other {# heures}}", - "last": "Dernier", - "last-prefix": "dernier", - "minutes": "{minutes, plural, 0 {minute} 1 {1 minute} other {# minutes}}", - "period": "de {{startTime}} à {{endTime}}", - "realtime": "Temps réel", - "seconds": "{seconds, plural, 0 {second} 1 {1 second} other {# seconds}}", - "time-period": "Période", - "hide": "Masquer" - }, - "user": { - "activation-email-sent-message": "Le courriel d'activation a été envoyé avec succès!", - "activation-link": "Lien d'activation utilisateur", - "activation-link-copied-message": "le lien d'activation de l'utilisateur a été copié dans le presse-papier", - "activation-link-text": "Pour activer l'utilisateur, utilisez le lien d'activation suivant: ", - "activation-method": "Méthode d'activation", - "add": "Ajouter un utilisateur", - "add-user-text": "Ajouter un nouvel utilisateur", - "always-fullscreen": "Toujours en plein écran", - "anonymous": "Anonyme", - "copy-activation-link": "Copier le lien d'activation", - "customer": "Client", - "customer-users": "Utilisateurs du client", - "default-dashboard": "Tableau de bord par défaut", - "delete": "Supprimer l'utilisateur", - "delete-user-text": "Attention, après la confirmation, l'utilisateur et toutes les données associées deviendront irrécupérables.", - "delete-user-title": "Êtes-vous sûr de vouloir supprimer l'utilisateur '{{userEmail}}'?", - "delete-users-action-title": "Supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}", - "delete-users-text": "Attention, après la confirmation, tous les utilisateurs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-users-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}?", - "description": "Description", - "details": "Détails", - "disable-account": "Désactiver le compte d'utilisateur", - "disable-account-message": "Le compte d'utilisateur a été désactivé avec succès!", - "display-activation-link": "Afficher le lien d'activation", - "email": "Email", - "email-required": "Email est requis.", - "enable-account": "Activer le compte d'utilisateur", - "enable-account-message": "Le compte d'utilisateur a été activé avec succès!", - "first-name": "Prénom", - "invalid-email-format": "Format de courrier électronique non valide", - "last-name": "Nom de famille", - "login-as-customer-user": "Se connecter en tant qu'utilisateur client", - "login-as-tenant-admin": "Connectez-vous en tant qu'administrateur Tenant", - "no-users-matching": "Aucun utilisateur correspondant à '{{entity}}' n'a été trouvé.", - "no-users-text": "Aucun utilisateur trouvé", - "resend-activation": "Renvoyer l'activation", - "select-user": "Sélectionner l'utilisateur", - "send-activation-mail": "Envoyer un mail d'activation", - "sys-admin": "Administrateur du système", - "tenant-admin": "Administrateur du Tenant", - "tenant-admins": "administrateurs du Tenant", - "user": "utilisateur", - "user-details": "Détails de l'utilisateur", - "user-required": "L'utilisateur est requis", - "users": "Utilisateurs" - }, - "value": { - "boolean": "booléen", - "boolean-value": "Valeur booléenne", - "double": "Double", - "double-value": "Valeur double", - "false": "Faux", - "integer": "Entier", - "integer-value": "Valeur entière", - "invalid-integer-value": "Valeur entière invalide", - "long": "Long", - "string": "String", - "string-value": "Valeur String", - "true": "Vrai", - "type": "Type de valeur" - }, - "widget": { - "add": "Ajouter un widget", - "add-resource": "Ajouter une ressource", - "add-widget-type": "Ajouter un nouveau type de widget", - "alarm": "Widget d'alarme", - "css": "CSS", - "datakey-settings-schema": "Schéma des paramètres de Data key", - "edit": "Modifier le widget", - "editor": " Editeur de widget", - "export": "Exporter widget", - "html": "HTML", - "javascript": "Javascript", - "latest-values": "Dernières valeurs", - "management": "Gestion des widgets", - "missing-widget-title-error": "Le titre du widget doit être spécifié!", - "no-data-found": "Aucune donnée trouvée", - "remove": "Supprimer le widget", - "remove-resource": "Supprimer une ressource", - "remove-widget-text": "Après la confirmation, le widget et toutes les données associées deviendront irrécupérables.", - "remove-widget-title": "Êtes-vous sûr de vouloir supprimer le widget '{{widgetTitle}}'?", - "remove-widget-type": "Supprimer le type de widget", - "remove-widget-type-text": "Après la confirmation, le type de widget et toutes les données associées deviendront irrécupérables.", - "remove-widget-type-title": "Êtes-vous sûr de vouloir supprimer le type de widget '{{widgetName}}'?", - "resource-url": "URL JavaScript / CSS", - "resources": "Ressources", - "rpc": "Widget de contrôle", - "run": "Exécuter un widget", - "save": "Enregistrer le widget", - "save-widget-type-as": "Enregistrer le type de widget sous", - "save-widget-type-as-text": "Veuillez saisir un nouveau titre de widget et / ou sélectionner un ensemble de widgets cibles", - "saveAs": "Enregistrer le widget sous", - "search-data": "Rechercher des données", - "select-widget-type": "Sélectionnez le type de widget", - "select-widgets-bundle": "Sélectionner un ensemble de widgets", - "settings-schema": "Schéma des paramétres", - "static": "Widget statique", - "tidy": "Nettoyer", - "timeseries": "Séries chronologiques", - "title": "Titre du widget", - "title-required": "Le titre du widget est requis.", - "toggle-fullscreen": "Basculer le mode plein écran", - "type": "Type de widget", - "unable-to-save-widget-error": "Impossible de sauvegarder le widget! Le widget a des erreurs!", - "undo": "Annuler les modifications du widget", - "widget-bundle": "Ensemble de widget", - "widget-library": "Bibliothèque de widgets", - "widget-saved": "Widget enregistré", - "widget-template-load-failed-error": "Impossible de charger le modéle de widget!", - "widget-type-load-error": "Le widget n'a pas été chargé à cause des erreurs suivantes:", - "widget-type-load-failed-error": "Impossible de charger le type de widget!", - "widget-type-not-found": "Problème de chargement de la configuration du widget.
Le type de widget associé a probablement été supprimé." - }, - "widget-action": { - "custom": "Action personnalisée", - "header-button": "Bouton d'en-tête de widget", - "open-dashboard": "Naviguer vers un autre tableau de bord", - "open-dashboard-state": "Naviguer vers un nouvel état du tableau de bord", - "open-right-layout": "Ouvrir la disposition du tableau de bord droite (vue mobile)", - "set-entity-from-widget": "Définir l'entité à partir du widget", - "target-dashboard": "Tableau de bord cible", - "target-dashboard-state": "État du tableau de bord cible", - "target-dashboard-state-required": "L'état du tableau de bord cible est requis", - "update-dashboard-state": "Mettre à jour l'état actuel du tableau de bord" - }, - "widget-config": { - "action": "Action", - "action-icon": "Icône", - "action-name": "Nom", - "action-name-not-unique": "Une autre action portant le même nom existe déjà.
Le nom de l'action doit être unique dans la même source d'action.", - "action-name-required": "Le nom de l'action est requis", - "action-source": "Source de l'action", - "action-source-required": "Une source d'action est requise.", - "action-type": "Type", - "action-type-required": "Le type d'action est requis.", - "actions": "Actions", - "add-action": "Ajouter une action", - "add-datasource": "Ajouter une source de données", - "advanced": "Avancé", - "alarm-source": "Source d'alarme", - "background-color": "couleur de fond", - "data": "Données", - "datasource-parameters": "Paramètres", - "datasource-type": "Type", - "datasources": "Sources de données", - "decimals": "Nombre de chiffres après virgule flottante", - "delete-action": "Supprimer l'action", - "delete-action-text": "Êtes-vous sûr de vouloir supprimer l'action du widget nommé '{{actionName}}'?", - "delete-action-title": "Supprimer l'action du widget", - "display-timewindow": "Afficher fenêtre de temps", - "display-legend": "Afficher la légende", - "display-title": "Afficher le titre", - "drop-shadow": "Ombre portée", - "edit-action": "Modifier l'action", - "enable-fullscreen": "Activer le plein écran", - "general-settings": "Paramètres généraux", - "height": "Hauteur", - "margin": "Marge", - "maximum-datasources": "Maximum {count, plural, 1 {1 datasource est autorisé.} other {# datasources sont autorisés}}", - "mobile-mode-settings": "Paramètres du mode mobile", - "order": "Ordre", - "padding": "Padding", - "remove-datasource": "Supprimer la source de données", - "search-actions": "Recherche d'actions", - "settings": "Paramètres", - "target-device": "Dispositif cible", - "text-color": "Couleur du texte", - "timewindow": "Fenêtre de temps", - "title": "Titre", - "title-style": "Style de titre", - "title-tooltip": "Tooltip de titre", - "units": "Symbole spécial à afficher à côté de la valeur", - "use-dashboard-timewindow": "Utiliser la fenêtre de temps du tableau de bord", - "widget-style": "Style du widget", - "display-icon": "Afficher l'icône du titre", - "icon-color": "Couleur de l'icône", - "icon-size": "Taille de l'icône" - }, - "widget-type": { - "create-new-widget-type": "Créer un nouveau type de widget", - "export": "Exporter le type de widget", - "export-failed-error": "Impossible d'exporter le type de widget: {{error}}", - "import": "Importer le type de widget", - "invalid-widget-type-file-error": "Impossible d'importer le type de widget: structure de données de type widget invalide.", - "widget-type-file": "Fichier de type Widget" - }, - "widgets": { - "date-range-navigator": { - "localizationMap": { - "Sun": "Dim.", - "Mon": "Lun.", - "Tue": "Mar.", - "Wed": "Mer.", - "Thu": "Jeu.", - "Fri": "Ven.", - "Sat": "Sam.", - "Jan": "Janv.", - "Feb": "Févr.", - "Mar": "Mars", - "Apr": "Avr.", - "May": "Mai", - "Jun": "Juin", - "Jul": "Juil.", - "Aug": "Août", - "Sep": "Sept.", - "Oct": "Oct.", - "Nov": "Nov.", - "Dec": "Déc.", - "January": "Janvier", - "February": "Février", - "March": "Mars", - "April": "Avril", - "June": "Juin", - "July": "Juillet", - "August": "Août", - "September": "Septembre", - "October": "Octobre", - "November": "Novembre", - "December": "Décembre", - "Custom Date Range": "Plage de dates personnalisée", - "Date Range Template": "Modèle de plage de dates", - "Today": "Aujourd'hui", - "Yesterday": "Hier", - "This Week": "Cette semaine", - "Last Week": "La semaine dernière", - "This Month": "Ce mois-ci", - "Last Month": "Le mois dernier", - "Year": "Année", - "This Year": "Cette année", - "Last Year": "L'année dernière", - "Date picker": "Sélecteur de date", - "Hour": "Heure", - "Day": "Journée", - "Week": "La semaine", - "2 weeks": "2 Semaines", - "Month": "Mois", - "3 months": "3 Mois", - "6 months": "6 Mois", - "Custom interval": "Intervalle personnalisé", - "Interval": "Intervalle", - "Step size": "Taille de pas", - "Ok": "Ok" - } - }, - "input-widgets": { - "attribute-not-allowed": "Le paramètre d'attribut ne peut pas être utilisé dans ce widget", - "date": "Date", - "discard-changes": "Annuler les modifications", - "entity-attribute-required": "L'attribut d'entité est requis", - "entity-timeseries-required": "Entité timeseries est requis", - "not-allowed-entity": "L'entité sélectionnée ne peut pas avoir d'attributs partagés", - "no-attribute-selected": "Aucun attribut n'est sélectionné", - "no-datakey-selected": "Aucune date n'est sélectionnée", - "no-entity-selected": "Aucune entité sélectionnée", - "no-image": "Pas d'image", - "no-support-web-camera": "Pas de webcam supportée", - "no-timeseries-selected": "Aucune série temporelle sélectionnée", - "switch-attribute-value": "Changer la valeur de l'attribut d'entité", - "switch-camera": "Changer de caméra", - "switch-timeseries-value": "Changer la valeur de l'entité série temporelle", - "take-photo": "Prendre une photo", - "time": "Temps", - "timeseries-not-allowed": "Le paramètre série temporelle ne peut pas être utilisé dans ce widget", - "update-failed": "Mise à jour a échoué", - "update-successful": "Mise à jour réussie", - "update-attribute": "Attribut de mise à jour", - "update-timeseries": "Mise à jour de la série temporelle", - "value": "Valeur" - } - }, - "widgets-bundle": { - "add": "Ajouter un groupe de widgets", - "add-widgets-bundle-text": "Ajouter un nouveau groupe de widgets", - "create-new-widgets-bundle": "Créer un nouveau groupe de widgets", - "current": "Groupe actuel", - "delete": "Supprimer le groupe de widgets", - "delete-widgets-bundle-text": "Attention, après la confirmation, le groupe de widgets et toutes les données associées deviendront irrécupérables.", - "delete-widgets-bundle-title": "Êtes-vous sûr de vouloir supprimer le groupe de widgets '{{widgetsBundleTitle}}'?", - "delete-widgets-bundles-action-title": "Supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}", - "delete-widgets-bundles-text": "Attention, après la confirmation, tous les groupes de widgets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", - "delete-widgets-bundles-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}?", - "details": "Détails", - "empty": "Le groupe de widgets est vide", - "export": "Exporter le groupe de widgets", - "export-failed-error": "Impossible d'exporter le groupe de widgets: {{error}}", - "import": "Importer un groupe de widgets", - "invalid-widgets-bundle-file-error": "Impossible d'importer un groupe de widgets: structure de données du groupe de widgets non valides.", - "no-widgets-bundles-matching": "Aucun groupe de widgets correspondant à {{widgetsBundle}} n'a été trouvé.", - "no-widgets-bundles-text": "Aucun groupe de widgets trouvé", - "system": "Système", - "title": "Titre", - "title-required": "Le titre est requis.", - "widgets-bundle-details": "Détails des groupes de widgets", - "widgets-bundle-file": "Fichier de groupe de widgets", - "widgets-bundle-required": "Un groupe de widgets est requis.", - "widgets-bundles": "Groupes de widgets" - } -} +{ + "access": { + "access-forbidden": "Accès interdit", + "access-forbidden-text": "Vous n'avez pas accès à cet emplacement!
Essayez de vous connecter avec un autre utilisateur si vous souhaitez toujours accéder à cet emplacement.", + "refresh-token-expired": "La session a expiré", + "refresh-token-failed": "Impossible de rafraîchir la session", + "unauthorized": "non autorisé", + "unauthorized-access": "accès non autorisé", + "unauthorized-access-text": "Vous devez vous connecter pour avoir accès à cette ressource!" + }, + "action": { + "activate": "Activer", + "add": "Ajouter", + "apply": "Appliquer", + "apply-changes": "Appliquer les modifications", + "assign": "Attribuer", + "back": "retour", + "cancel": "Annuler", + "clear-search": "Effacer la recherche", + "close": "Fermer", + "continue": "Continue", + "copy": "Copier", + "copy-reference": "Copier la référence", + "create": "Créer", + "decline-changes": "Refuser les modifications", + "delete": "Supprimer", + "discard-changes": "Annuler les modifications", + "drag": "Drag", + "edit": "Modifier", + "edit-mode": "Mode édition", + "enter-edit-mode": "Entrer en mode édition", + "export": "Exporter", + "import": "Importer", + "make-private": "Rendre privé", + "no": "Non", + "ok": "OK", + "paste": "coller", + "paste-reference": "Coller référence", + "refresh": "Rafraîchir", + "remove": "Supprimer", + "run": "Exécuter", + "save": "Enregistrer", + "saveAs": "Enregistrer sous", + "search": "Rechercher", + "share": "Partager", + "share-via": "Partager via {{provider}}", + "sign-in": "Connectez-vous!", + "suspend": "Suspendre", + "unassign": "Retirer", + "undo": "Annuler", + "update": "mise à jour", + "view": "Afficher", + "yes": "Oui" + }, + "admin": { + "base-url": "URL de base", + "base-url-required": "L'URL de base est requise.", + "enable-tls": "Activer TLS", + "tls-version": "Version TLS", + "enter-tls-version" : "Entrez la version TLS", + "general": "Général", + "general-settings": "Paramètres généraux", + "mail-from": "Mail de", + "mail-from-required": "Mail de est requis.", + "outgoing-mail": "courrier sortant", + "outgoing-mail-settings": "Paramètres de courrier sortant", + "send-test-mail": "Envoyer un mail de test", + "smtp-host": "Hôte SMTP", + "smtp-host-required": "L'hôte SMTP est requis.", + "smtp-port": "Port SMTP", + "smtp-port-invalid": "Cela ne ressemble pas à un port smtp valide.", + "smtp-port-required": "Vous devez fournir un port smtp.", + "smtp-protocol": "Protocole SMTP", + "system-settings": "Paramètres système", + "test-mail-sent": "Le courrier de test a été envoyé avec succés!", + "timeout-invalid": "Cela ne ressemble pas à un délai d'expiration valide.", + "timeout-msec": "Délai (msec)", + "timeout-required": "Le délai est requis.", + "security-settings": "Les paramètres de sécurité", + "password-policy": "Politique de mot de passe", + "minimum-password-length": "Longueur minimale du mot de passe", + "minimum-password-length-required": "La longueur minimale du mot de passe est requise", + "minimum-password-length-range": "La longueur minimale du mot de passe doit être comprise entre 5 et 50.", + "minimum-uppercase-letters": "Nombre minimum de lettres majuscules", + "minimum-uppercase-letters-range": "Le nombre minimum de lettres majuscules ne peut pas être négatif", + "minimum-lowercase-letters": "Nombre minimum de lettres minuscules", + "minimum-lowercase-letters-range": "Le nombre minimum de lettres minuscules ne peut pas être négatif", + "minimum-digits": "Nombre minimum de chiffres", + "minimum-digits-range": "Le nombre minimum de chiffres ne peut pas être négatif", + "minimum-special-characters": "Nombre minimum de caractères spéciaux", + "minimum-special-characters-range": "Le nombre minimum de caractères spéciaux ne peut pas être négatif", + "password-expiration-period-days": "Délai d'expiration du mot de passe en jours", + "password-expiration-period-days-range": "La période d'expiration du mot de passe en jours ne peut pas être négative", + "password-reuse-frequency-days": "Fréquence de réutilisation du mot de passe en jours", + "password-reuse-frequency-days-range": "La fréquence de réutilisation du mot de passe en jours ne peut être négative", + "general-policy": "Politique générale", + "max-failed-login-attempts": "Nombre maximal de tentatives de connexion infructueuses avant que le compte ne soit verrouillé", + "minimum-max-failed-login-attempts-range": "Le nombre maximal de tentatives de connexion ayant échoué ne peut pas être négatif", + "user-lockout-notification-email": "En cas de verrouillage du compte d'utilisateur, envoyez une notification par courrier électronique." + }, + "aggregation": { + "aggregation": "agrégation", + "avg": "Moyenne", + "count": "Compte", + "function": "Fonction d'agrégation de données", + "group-interval": "Intervalle de regroupement", + "limit": "Valeurs maximales", + "max": "Max", + "min": "Min", + "none": "Aucune", + "sum": "Somme" + }, + "alarm": { + "ack-time": "Heure d'acquittement", + "acknowledge": "Acquitter", + "aknowledge-alarm-text": "Êtes-vous sûr de vouloir reconnaître l'alarme?", + "aknowledge-alarm-title": "Reconnaître l'alarme", + "aknowledge-alarms-text": "Êtes-vous sûr de vouloir acquitter {count, plural, 1 {1 alarme} other {# alarmes}}?", + "aknowledge-alarms-title": "Acquitter {count, plural, 1 {1 alarme} other {# alarmes}}", + "alarm": "Alarme", + "alarm-details": "Détails de l'alarme", + "alarm-required": "Une alarme est requise", + "alarm-status": "État d'alarme", + "alarm-status-filter": "Filtre d'état d'alarme", + "alarms": "Alarmes", + "clear": "Effacer", + "clear-alarm-text": "Êtes-vous sûr de vouloir effacer l'alarme?", + "clear-alarm-title": "Effacer l'alarme", + "clear-alarms-text": "Êtes-vous sûr de vouloir effacer {count, plural, 1 {1 alarme} other {# alarmes}}?", + "clear-alarms-title": "Effacer {count, plural, 1 {1 alarme} other {# alarmes}}", + "clear-time": "Heure d'éffacement", + "created-time": "Heure de création", + "details": "Détails", + "display-status": { + "ACTIVE_ACK": "Active acquittée", + "ACTIVE_UNACK": "Active non acquittée", + "CLEARED_ACK": "effacée acquittée", + "CLEARED_UNACK": "effacée non acquittée" + }, + "end-time": "Heure de fin", + "min-polling-interval-message": "Un intervalle d'interrogation d'au moins 1 seconde est autorisé.", + "no-alarms-matching": "Aucune alarme correspondant à {{entity}} n'a été trouvée. ", + "no-alarms-prompt": "Aucune alarme", + "no-data": "Aucune donnée à afficher", + "originator": "Source", + "originator-type": "Type de Source", + "polling-interval": "Intervalle d'interrogation des alarmes (sec)", + "polling-interval-required": "L'intervalle d'interrogation des alarmes est requis.", + "search": "Rechercher des alarmes", + "search-status": { + "ACK": "acquitté", + "ACTIVE": "active", + "ANY": "Toutes", + "CLEARED": "effacée", + "UNACK": "non acquittée" + }, + "select-alarm": "Sélectionnez une alarme", + "selected-alarms": "{count, plural, 1 {1 alarme} other {# alarmes}} sélectionnées", + "severity": "Gravité", + "severity-critical": "Critique", + "severity-indeterminate": "indéterminée", + "severity-major": "Majeure", + "severity-minor": "mineure", + "severity-warning": "Avertissement", + "start-time": "Heure de début", + "status": "État", + "type": "Type" + }, + "alias": { + "add": "Ajouter un alias", + "all-entities": "Toutes les entités", + "any-relation": "toutes", + "default-entity-parameter-name": "Par défaut", + "default-state-entity": "Entité d'état par défaut", + "duplicate-alias": "Un alias portant le même nom existe déjà.", + "edit": "Modifier l'alias", + "entity-filter": "Filtre d'entité", + "entity-filter-no-entity-matched": "Aucune entité correspondant au filtre spécifié n'a été trouvée.", + "filter-type": "Type de filtre", + "filter-type-asset-search-query": "requête de recherche d'actifs", + "filter-type-asset-search-query-description": "Actifs de types {{assetTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-asset-type": "type d'actif", + "filter-type-asset-type-and-name-description": "Actifs de type '{{assetType}}' et dont le nom commence par '{{prefix}}'", + "filter-type-asset-type-description": "Actifs de type '{{assetType}}'", + "filter-type-device-search-query": "Requête de recherche de dispositif", + "filter-type-device-search-query-description": "Dispositifs de types {{deviceTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-device-type": "Type de dispositif", + "filter-type-device-type-and-name-description": "Dispositifs de type '{{deviceType}}' et dont le nom commence par '{{prefix}}'", + "filter-type-device-type-description": "Dispositifs de type '{{deviceType}}'", + "filter-type-entity-list": "Liste d'entités", + "filter-type-entity-name": "Nom d'entité", + "filter-type-entity-view-search-query": "Requête de recherche vue d'entité", + "filter-type-entity-view-search-query-description": "Vues d'entité avec les types {{entityViewTypes}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-entity-view-type": "Type de vue d'entité", + "filter-type-entity-view-type-and-name-description": "Vues d'entité de type '{{entityView}}' et dont le nom commence par '{{prefix}}'", + "filter-type-entity-view-type-description": "Vues d'entité de type '{{entityView}}'", + "filter-type-relations-query": "Interrogation des relations", + "filter-type-relations-query-description": "{{entities}} ayant {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-required": "Le type de filtre est requis.", + "filter-type-single-entity": "Entité unique", + "filter-type-state-entity": "Entité de l'état du tableau de bord", + "filter-type-state-entity-description": "Entité extraite des paramétres d'état du tableau de bord", + "max-relation-level": "Niveau de relation maximum", + "name": "Nom de l'alias", + "name-required": "Le nom d'alias est requis", + "no-entity-filter-specified": "Aucun filtre d'entité spécifié", + "resolve-multiple": "Résoudre en plusieurs entités", + "root-entity": "Entité racine", + "root-state-entity": "Utiliser l'entité d'état du tableau de bord en tant que racine", + "state-entity": "Entité d'état du tableau de bord", + "state-entity-parameter-name": "Nom du paramétre d'entité d'état", + "unlimited-level": "niveau illimité" + }, + "asset": { + "add": "Ajouter un actif", + "add-asset-text": "Ajouter un nouvel actif", + "any-asset": "Tout actif", + "asset": "Actif", + "asset-details": "Détails de l'actif", + "asset-file": "Actif file", + "asset-public": "L'actif est public", + "asset-required": "Actif requis", + "asset-type": "Type d'actif", + "asset-type-list-empty": "Aucun type d'actif sélectionné.", + "asset-type-required": "Le type d'actif est requis.", + "asset-types": "Types d'actif", + "assets": "Actifs", + "assign-asset-to-customer": "Attribuer des actifs au client", + "assign-asset-to-customer-text": "Veuillez sélectionner les actifs à attribuer au client", + "assign-assets": "Attribuer des actifs", + "assign-assets-text": "Attribuer {count, plural, 1 {1 asset} other {# assets}} au client", + "assign-new-asset": "Attribuer un nouvel Asset", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les actifs", + "assignedToCustomer": "attribué au client", + "copyId": "Copier l'Id de l'actif", + "delete": "Supprimer un actif", + "delete-asset-text": "Faites attention, après la confirmation, l'actif et toutes les données associées deviendront irrécupérables.", + "delete-asset-title": "Êtes-vous sûr de vouloir supprimer l'actif '{{assetName}}'?", + "delete-assets": "Supprimer des actifs", + "delete-assets-action-title": "Supprimer {count, plural, 1 {1 asset} other {# assets}}", + "delete-assets-text": "Attention, après la confirmation, tous les actifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-assets-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 asset} other {# assets}}?", + "description": "Description", + "details": "Détails", + "enter-asset-type": "Entrez le type d'actif", + "events": "Evénements", + "idCopiedMessage": "L'Id d'asset a été copié dans le presse-papier", + "import": "Import actifs", + "make-private": "Rendre l'actif privé", + "make-private-asset-text": "Après la confirmation, l'actif et toutes ses données seront rendus privés et ne seront pas accessibles par d'autres.", + "make-private-asset-title": "Êtes-vous sûr de vouloir rendre l'actif '{{assetName}}' privé '?", + "make-public": "Rendre l'actif public", + "make-public-asset-text": "Après la confirmation, l'asset et toutes ses données seront rendus publics et accessibles aux autres.", + "make-public-asset-title": "Êtes-vous sûr de vouloir rendre l'actif '{{assetName}}' public '?", + "management": "Gestion d'actifs", + "name": "Nom", + "name-required": "Nom est requis.", + "name-starts-with": "Le nom de l'actif commence par", + "no-asset-types-matching": "Aucun type d'actif correspondant à {{entitySubtype}} n'a été trouvé. ", + "no-assets-matching": "Aucun actif correspondant à {{entity}} n'a été trouvé. ", + "no-assets-text": "Aucun actif trouvé", + "public": "Public", + "select-asset": "Sélectionner un actif", + "select-asset-type": "Sélectionner le type d'actif", + "type": "Type", + "type-required": "Le type est requis.", + "unassign-asset": "Retirer l'actif", + "unassign-asset-text": "Après la confirmation, l'actif sera non attribué et ne sera pas accessible au client.", + "unassign-asset-title": "Êtes-vous sûr de vouloir retirer l'attribution de l'actif '{{assetName}}'?", + "unassign-assets": "Retirer les actifs", + "unassign-assets-action-title": "Retirer {count, plural, 1 {1 asset} other {# assets}} du client", + "unassign-assets-text": "Après la confirmation, tous les actifs sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", + "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?", + "unassign-from-customer": "Retirer du client", + "view-assets": "Afficher les actifs", + "label": "Label" + }, + "attribute": { + "add": "Ajouter un attribut", + "add-to-dashboard": "Ajouter au tableau de bord", + "add-widget-to-dashboard": "Ajouter un widget au tableau de bord", + "attributes": "Attributs", + "attributes-scope": "Étendue des attributs d'entité", + "delete-attributes": "Supprimer les attributs", + "delete-attributes-text": "Attention, après la confirmation, tous les attributs sélectionnés seront supprimés.", + "delete-attributes-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 attribut} other {# attributs}}?", + "enter-attribute-value": "Entrez la valeur de l'attribut", + "key": "Clé", + "key-required": "La Clé d'attribut est requise.", + "last-update-time": "Dernière mise à jour", + "latest-telemetry": "Dernière télémétrie", + "next-widget": "Widget suivant", + "prev-widget": "Widget précédent", + "scope-client": "Attributs du client", + "scope-latest-telemetry": "Dernière télémétrie", + "scope-server": "Attributs du serveur", + "scope-shared": "Attributs partagés", + "selected-attributes": "{count, plural, 1 {1 attribut} other {# attributs}} sélectionnés", + "selected-telemetry": "{count, plural, 1 {1 unité de télémétrie} other {# unités de télémétrie}} sélectionnées", + "show-on-widget": "Afficher sur le widget", + "value": "Valeur", + "value-required": "La valeur d'attribut est obligatoire.", + "widget-mode": "Mode du widget" + }, + "audit-log": { + "action-data": "Action data", + "audit": "Audit", + "audit-log-details": "Détails du journal d'audit", + "audit-logs": "Journaux d'audit", + "clear-search": "Effacer la recherche", + "details": "Détails", + "entity-name": "Nom de l'entité", + "entity-type": "Type d'entité", + "failure-details": "Détails de l'échec", + "no-audit-logs-prompt": "Aucun journal trouvé", + "search": "Rechercher les journaux d'audit", + "status": "État", + "status-failure": "Échec", + "status-success": "Succès", + "timestamp": "Horodatage", + "type": "Type", + "type-activated": "Activé", + "type-added": "Ajouté", + "type-alarm-ack": "Acquitté", + "type-alarm-clear": "Effacé", + "type-assigned-to-customer": "Attribué au client", + "type-attributes-deleted": "Attributs supprimés", + "type-attributes-read": "Attributs lus", + "type-attributes-updated": "Attributs mis à jour", + "type-credentials-read": "Lecture des informations d'identification", + "type-credentials-updated": "Informations d'identification actualisées", + "type-deleted": "Supprimé", + "type-login": "Login", + "type-logout": "Connectez - Out", + "type-lockout": "Verrouillage", + "type-relation-add-or-update": "Relation mise à jour", + "type-relation-delete": "Relation supprimée", + "type-relations-delete": "Toutes les relations ont été supprimées", + "type-rpc-call": "Appel RPC", + "type-suspended": "Suspendu", + "type-unassigned-from-customer": "Non attribué du client", + "type-updated": "Mise à jour", + "user": "Utilisateur" + }, + "common": { + "enter-password": "Entrez le mot de passe", + "enter-search": "Entrez la recherche", + "enter-username": "Entrez le nom d'utilisateur", + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "confirm-on-exit": { + "html-message": "Vous avez des modifications non enregistrées.
Êtes-vous sûr de vouloir quitter cette page?", + "message": "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir quitter cette page?", + "title": "Modifications non enregistrées" + }, + "contact": { + "address": "Adresse", + "address2": "adresse 2", + "city": "Ville", + "country": "Pays", + "email": "Email", + "no-address": "Pas d'adresse", + "phone": "Téléphone", + "postal-code": "Code postal", + "postal-code-invalid": "Format de code postal / code postal invalide", + "state": "Province" + }, + "content-type": { + "binary": "Binaire (Base64)", + "json": "Json", + "text": "Texte" + }, + "custom": { + "widget-action": { + "action-cell-button": "Bouton de cellule d'action", + "marker-click": "Sur le marqueur cliquez", + "row-click": "Au rang, cliquez", + "polygon-click": "Cliquez sur le polygone", + "tooltip-tag-action": "Action de balise d'info-bulle", + "node-selected": "Sur le noeud sélectionné", + "element-click": "Sur l'élément HTML, cliquez sur", + "pie-slice-click": "Sur tranche cliquez", + "row-double-click": "Sur la ligne double clic" + } + }, + "customer": { + "add": "Ajouter un client", + "add-customer-text": "Ajouter un nouveau client", + "assets": "Actifs du client", + "copyId": "Copier l'id du client", + "customer": "Client", + "customer-details": "Détails du client", + "customer-required": "Le client est requis", + "customers": "Clients", + "dashboard": "Tableau de bord du client", + "dashboards": "tableaux de bord du client", + "default-customer": "Client par défaut", + "default-customer-required": "Le client par défaut est requis pour déboguer le tableau de bord au niveau du Tenant", + "delete": "Supprimer le client", + "delete-customer-text": "Faites attention, après la confirmation, le client et toutes les données associées deviendront irrécupérables.", + "delete-customer-title": "Êtes-vous sûr de vouloir supprimer le client '{{customerTitle}}'?", + "delete-customers-action-title": "Supprimer {count, plural, 1 {1 customer} other {# customers}}", + "delete-customers-text": "Faites attention, après la confirmation, tous les clients sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-customers-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 customer} other {# customers}}?", + "description": "Description", + "details": "Détails", + "devices": "Dispositifs du client", + "entity-views": "Vues de l'entité client", + "events": "Événements", + "idCopiedMessage": "L'Id du client a été copié dans le presse-papier", + "manage-assets": "Gérer les actifs", + "manage-customer-assets": "Gérer les actifs du client", + "manage-customer-dashboards": "Gérer les tableaux de bord du client", + "manage-customer-devices": "Gérer les dispositifs du client", + "manage-customer-users": "Gérer les utilisateurs du client", + "manage-dashboards": "Gérer les tableaux de bord", + "manage-devices": "Gérer les dispositifs", + "manage-public-assets": "Gérer les actifs publics", + "manage-public-dashboards": "Gérer les tableaux de bord publics", + "manage-public-devices": "Gérer les dispositifs publics", + "manage-users": "Gérer les utilisateurs", + "management": "Gestion des clients", + "no-customers-matching": "Aucun client correspondant à '{{entity}} n'a été trouvé.", + "no-customers-text": "Aucun client trouvé", + "public-assets": "Actifs publics", + "public-dashboards": "Tableaux de bord publics", + "public-devices": "Dispositifs publics", + "public-entity-views": "Vues d'entités publiques", + "select-customer": "Sélectionner un client", + "select-default-customer": "Sélectionnez le client par défaut", + "title": "Titre", + "title-required": "Le titre est requis." + }, + "dashboard": { + "add": "Ajouter un tableau de bord", + "add-dashboard-text": "Ajouter un nouveau tableau de bord", + "add-state": "Ajouter un état du tableau de bord", + "add-widget": "Ajouter un nouveau widget", + "alias-resolution-error-title": "Erreur de configuration des alias de tableau de bord", + "assign-dashboard-to-customer": "Attribuer des tableaux de bord au client", + "assign-dashboard-to-customer-text": "Veuillez sélectionner les tableaux de bord à affecter au client", + "assign-dashboards": "Attribuer des tableaux de bord", + "assign-dashboards-text": "Attribuer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} aux clients", + "assign-new-dashboard": "Attribuer un nouveau tableau de bord", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les tableaux de bord", + "assign-to-customers": "Attribuer des tableaux de bord aux clients", + "assign-to-customers-text": "Veuillez sélectionner les clients pour attribuer les tableaux de bord", + "assigned-customers": "clients affectés", + "assignedToCustomer": "Attribué au client", + "assignedToCustomers": "attribué aux clients", + "autofill-height": "Hauteur de remplissage automatique", + "background-color": "Couleur de fond", + "background-image": "Image d'arriére-plan", + "background-size-mode": "Mode de taille d'arriére-plan", + "close-toolbar": "Fermer la barre d'outils", + "columns-count": "Nombre de colonnes", + "columns-count-required": "Le nombre de colonnes est requis.", + "configuration-error": "Erreur de configuration", + "copy-public-link": "Copier le lien public", + "create-new": "Créer un nouveau tableau de bord", + "create-new-dashboard": "Créer un nouveau tableau de bord", + "create-new-widget": "Créer un nouveau widget", + "dashboard": "Tableau de bord", + "dashboard-details": "Détails du tableau de bord", + "dashboard-file": "Fichier du tableau de bord", + "dashboard-import-missing-aliases-title": "Configurer les alias utilisés par le tableau de bord importé", + "dashboard-required": "Le tableau de bord est requis.", + "dashboards": "Tableaux de bord", + "delete": "Supprimer le tableau de bord", + "delete-dashboard-text": "Faites attention, après la confirmation, le tableau de bord et toutes les données associées deviendront irrécupérables.", + "delete-dashboard-title": "Êtes-vous sûr de vouloir supprimer le tableau de bord '{{dashboardTitle}}'?", + "delete-dashboards": "Supprimer les tableaux de bord", + "delete-dashboards-action-title": "Supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}", + "delete-dashboards-text": "Attention, après la confirmation, tous les tableaux de bord sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-dashboards-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "delete-state": "Supprimer l'état du tableau de bord", + "delete-state-text": "Etes-vous sûr de vouloir supprimer l'état du tableau de bord avec le nom '{{stateName}}'?", + "delete-state-title": "Supprimer l'état du tableau de bord", + "description": "Description", + "details": "Détails", + "display-dashboard-export": "Afficher l'exportation", + "display-dashboard-timewindow": "Afficher fenêtre de temps", + "display-dashboards-selection": "Afficher la sélection des tableaux de bord", + "display-entities-selection": "Afficher la sélection des entités", + "display-title": "Afficher le titre du tableau de bord", + "drop-image": "Déposer une image ou cliquez pour sélectionner un fichier à télécharger.", + "edit-state": "Modifier l'état du tableau de bord", + "export": "Exporter le tableau de bord", + "export-failed-error": "Impossible d'exporter le tableau de bord: {{error}}", + "hide-details": "Masquer les détails", + "horizontal-margin": "Marge horizontale", + "horizontal-margin-required": "Une valeur de marge horizontale est requise.", + "import": "Importer le tableau de bord", + "import-widget": "Importer un widget", + "invalid-aliases-config": "Impossible de trouver des dispositifs correspondant à certains filtres d'alias.
Veuillez contacter votre administrateur pour résoudre ce problème.", + "invalid-dashboard-file-error": "Impossible d'importer le tableau de bord: structure de données du tableau de bord non valide", + "invalid-widget-file-error": "Impossible d'importer le widget: structure de données de widget invalide.", + "is-root-state": "État racine", + "make-private": "Rendre privé le tableau de bord", + "make-private-dashboard": "Rendre privé le tableau de bord", + "make-private-dashboard-text": "Après la confirmation, le tableau de bord sera rendu privé et ne sera plus accessible aux autres.", + "make-private-dashboard-title": "Êtes-vous sûr de vouloir rendre le tableau de bord '{{dashboardTitle}}' privé?", + "make-public": "Rendre public le tableau de bord", + "manage-assigned-customers": "Gérer les clients affectés", + "manage-states": "Gérer les états du tableau de bord", + "management": "Gestion du tableau de bord", + "max-columns-count-message": "Seulement 1000 colonnes maximum sont autorisées.", + "max-horizontal-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge horizontale maximale.", + "max-mobile-row-height-message": "Seuls 200 pixels sont autorisés en tant que valeur maximale de hauteur de ligne mobile.", + "max-vertical-margin-message": "Seulement 50 sont autorisés en tant que valeur de marge verticale maximale.", + "min-columns-count-message": "Seul un nombre minimum de 10 colonnes est autorisé.", + "min-horizontal-margin-message": "Seul 0 est autorisé comme valeur de marge horizontale minimale.", + "min-mobile-row-height-message": "Seuls 5 pixels sont autorisés en tant que valeur minimale de hauteur de ligne mobile.", + "min-vertical-margin-message": "Seul 0 est autorisé comme valeur de marge verticale minimale.", + "mobile-layout": "Paramètres de mise en page mobiles", + "mobile-row-height": "Hauteur de ligne mobile, px", + "mobile-row-height-required": "Une valeur de hauteur de ligne mobile est requise.", + "new-dashboard-title": "Nouveau titre du tableau de bord", + "no-dashboards-matching": "Aucun tableau de bord correspondant à {{entity}} n'a été trouvé. ", + "no-dashboards-text": "Aucun tableau de bord trouvé", + "no-image": "Aucune image sélectionnée", + "no-widgets": "Aucun widget configuré", + "open-dashboard": "Ouvrir le tableau de bord", + "open-toolbar": "Ouvrir la barre d'outils du tableau de bord", + "public": "Public", + "public-dashboard-notice": " Remarque: N'oubliez pas de rendre publics les dispositifs associés pour accéder à leurs données.", + "public-dashboard-text": "Votre tableau de bord {{dashboardTitle}} est maintenant public et accessible via le lien public : ", + "public-dashboard-title": "Le tableau de bord est maintenant public", + "public-link": "Lien public", + "public-link-copied-message": "Le lien public du tableau de bord a été copié dans le presse-papier", + "search-states": "Recherche des états du tableau de bord", + "select-dashboard": "Sélectionner le tableau de bord", + "select-devices": "Selectionner les dispositifs", + "select-existing": "Sélectionnez un tableau de bord existant", + "select-state": "Sélectionnez l'état cible", + "select-widget-subtitle": "Liste des types de widgets disponibles", + "select-widget-title": "Sélectionner un widget", + "selected-states": "{count, plural, 1 {1 état du tableau de bord} other {# états du tableau de bord}} sélectionnés", + "set-background": "Définir l'arrière-plan", + "settings": "Paramètres", + "show-details": "Afficher les détails", + "socialshare-text": "'{{dashboardTitle}}' propulsé par ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' propulsé par ThingsBoard", + "state": "État du tableau de bord", + "state-controller": "Contrôleur d'état", + "state-id": "ID d'état", + "state-id-exists": "L'état du tableau de bord avec le même Id existe déjà.", + "state-id-required": "L'Id d'état du tableau de bord est requis.", + "state-name": "Nom", + "state-name-required": "Le nom de l'état du tableau de bord est requis", + "states": "États du tableau de bord", + "title": "Titre", + "title-color": "Couleur du titre", + "title-required": "Le titre est requis.", + "toolbar-always-open": "Garder la barre d'outils ouverte", + "unassign-dashboard": "Retirer le tableau de bord", + "unassign-dashboard-text": "Après la confirmation, le tableau de bord ne sera pas attribué et ne sera pas accessible au client.", + "unassign-dashboard-title": "Êtes-vous sûr de vouloir annuler l'affectation du tableau de bord '{{dashboardTitle}}'?", + "unassign-dashboards": "Retirer les tableaux de bord", + "unassign-dashboards-action-text": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} des clients", + "unassign-dashboards-action-title": "Annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}} du client", + "unassign-dashboards-text": "Après la confirmation, tous les tableaux de bord sélectionnés ne seront pas attribués et ne seront pas accessibles au client.", + "unassign-dashboards-title": "Etes-vous sûr de vouloir annuler l'affectation {count, plural, 1 {1 tableau de bord} other {# tableaux de bord}}?", + "unassign-from-customer": "Retirer du client", + "unassign-from-customers": "Retirer les tableaux de bord des clients", + "unassign-from-customers-text": "Veuillez sélectionner les clients à annuler l'affectation du ou des tableaux de bord", + "vertical-margin": "Marge verticale", + "vertical-margin-required": "Une valeur de marge verticale est requise", + "view-dashboards": "Afficher les tableaux de bord", + "widget-file": "Fichier du Widget", + "widget-import-missing-aliases-title": "Configurer les alias utilisés par le widget importé", + "widgets-margins": "Marge entre les widgets" + }, + "datakey": { + "advanced": "Avancé", + "alarm": "Champs d'alarme", + "alarm-fields-required": "Les champs d'alarme sont obligatoires.", + "attributes": "Attributs", + "color": "Couleur", + "configuration": "Configuration de la clé de données", + "data-generation-func": "Fonction de génération de données", + "decimals": "Nombre de chiffres après virgule flottante", + "function-types": "Types de fonctions", + "function-types-required": "Les types de fonctions sont obligatoires", + "label": "Label", + "maximum-function-types": "Maximum {count, plural, 1 {1 type de fonction est autorisé.} other {# types de fonctions sont autorisés}}", + "maximum-timeseries-or-attributes": "Maximum {count, plural, 1 {1 timeseries / attribut est autorisé.} other {# timeseries / attributs sont autorisés}}", + "prev-orig-value-description": "valeur précédente d'origine;", + "prev-value-description": "résultat de l'appel de fonction précédent;", + "settings": "Paramètres", + "time-description": "horodatage de la valeur actuelle;", + "time-prev-description": "horodatage de la valeur précédente;", + "timeseries": "Timeseries", + "timeseries-or-attributes-required": "Les timeseries / attributs d'entité sont obligatoires.", + "timeseries-required": "Les Timeseries de l'entité sont obligatoires.", + "units": "Symbole spécial à afficher à côté de la valeur", + "use-data-post-processing-func": "Utiliser la fonction de post-traitement des données", + "value-description": "la valeur actuelle;" + }, + "datasource": { + "add-datasource-prompt": "Veuillez ajouter une source de données", + "name": "Nom", + "type": "Type de source de données" + }, + "datetime": { + "date-from": "Date de", + "date-to": "Date à", + "time-from": "Heure de", + "time-to": "Heure à" + }, + "details": { + "edit-mode": "Mode édition", + "toggle-edit-mode": "Activer le mode édition" + }, + "device": { + "access-token": "Jeton d'accès", + "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 20 caractéres.", + "access-token-required": "Le jeton d'accès est requis.", + "accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier", + "add": "Ajouter un dispositif", + "add-alias": "Ajouter un alias de dispositif", + "add-device-text": "Ajouter un nouveau dispositif", + "alias": "Alias", + "alias-required": "Un alias du dispositif est requis.", + "aliases": "Alias des dispositifs", + "any-device": "N'importe quel dispositif", + "assign-device-to-customer": "Affecter des dispositifs au client", + "assign-device-to-customer-text": "Veuillez sélectionner les dispositif à affecter au client", + "assign-devices": "Attribuer des dispositifs", + "assign-devices-text": "Attribuer {count, plural, 1 {1 dispositif} other {# dispositifs}} au client", + "assign-new-device": "Attribuer un nouveau dispositif", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client pour attribuer le ou les dispositifs", + "assignedToCustomer": "Attribué au client", + "configure-alias": "Configurer '{{alias}}' alias", + "copyAccessToken": "Copier le jeton d'accès", + "copyId": "Copier l'Id du dispositif", + "create-new-alias": "Créez un nouveau!", + "create-new-key": "Créez un nouveau!", + "credentials": "Informations d'identification", + "credentials-type": "Type d'identification", + "delete": "Supprimer le dispositif", + "delete-device-text": "Faites attention, après la confirmation, le dispositif et toutes les données associées deviendront irrécupérables.", + "delete-device-title": "Êtes-vous sûr de vouloir supprimer le dispositif '{{deviceName}}'?", + "delete-devices": "Supprimer les dispositifs", + "delete-devices-action-title": "Supprimer {count, plural, 1 {1 device} other {# devices}}", + "delete-devices-text": "Faites attention, après la confirmation, tous les dispositifs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-devices-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 device} other {# devices}}?", + "description": "Description", + "details": "Détails", + "device": "Dispositif", + "device-alias": "Alias ​​du dispositif", + "device-credentials": "Informations d'identification du dispositif", + "device-details": "Détails du dispositif", + "device-list": "Liste des dispositifs", + "device-list-empty": "Aucun dispositif sélectionné.", + "device-name-filter-no-device-matched": "Aucun dispositif commençant par '{{device}} n'a été trouvé.", + "device-name-filter-required": "Le filtre de nom de dispositif est requis.", + "device-public": "Le dispositif est public", + "device-required": "Le dispositif est requis.", + "device-type": "Type de dispositif", + "device-type-list-empty": "Aucun type de dispositif sélectionné.", + "device-type-required": "Le type de dispositif est requis.", + "device-types": "Types de dispositif", + "devices": "Dispositifs", + "duplicate-alias-error": "Alias ??en double trouvé '{{alias}}'.
Les alias de dispositifs doivent être uniques dans le tableau de bord.", + "enter-device-type": "Entrez le type de dispositif", + "events": "Événements", + "idCopiedMessage": "l'Id du dispositif a été copié dans le presse-papiers", + "is-gateway": "Est une passerelle", + "label": "Label", + "make-private": "Rendre le dispositif privé", + "make-private-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendues privées et ne seront pas accessibles par d'autres.", + "make-private-device-title": "Êtes-vous sûr de vouloir rendre le dispositif {{deviceName}} privé?", + "make-public": "Rendre le dispositif public", + "make-public-device-text": "Après la confirmation, le dispositif et toutes ses données seront rendus publics et accessibles par d'autres.", + "make-public-device-title": "Êtes-vous sûr de vouloir rendre le dispositif {{deviceName}} 'public?", + "manage-credentials": "Gérer les informations d'identification", + "management": "Gestion des dispositifs", + "name": "Nom", + "name-required": "Le nom est requis.", + "name-starts-with": "Le nom du dispositif commence par", + "no-alias-matching": "'{{alias}}' introuvable.", + "no-aliases-found": "Aucun alias trouvé.", + "no-device-types-matching": "Aucun type de dispositif correspondant à {{entitySubtype}} n'a été trouvé.", + "no-devices-matching": "Aucun dispositif correspondant à '{{entity}} n'a été trouvé.", + "no-devices-text": "Aucun dispositif trouvé", + "no-key-matching": "'{{key}}' introuvable.", + "no-keys-found": "Aucune clé trouvée", + "public": "Public", + "remove-alias": "Supprimer l'alias du dispositif", + "rsa-key": "Clé publique RSA", + "rsa-key-required": "La clé publique RSA est requise.", + "secret": "Secret", + "secret-required": "Code secret est requis.", + "select-device": "Selectionner un dispositif", + "select-device-type": "Sélectionner le type d'appareil", + "unable-delete-device-alias-text": "L'alias du dispositif '{{deviceAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", + "unable-delete-device-alias-title": "Impossible de supprimer l'alias du dispositif", + "unassign-device": "Annuler l'affectation du dispositif", + "unassign-device-text": "Après la confirmation, le dispositif ne sera pas attribué et ne sera pas accessible au client.", + "unassign-device-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{deviceName}} '?", + "unassign-devices": "Annuler l'affectation des dispositifs", + "unassign-devices-action-title": "Annuler l'affectation de {count, plural, 1 {1 device} other {#devices}} du client", + "unassign-devices-text": "Après la confirmation, tous les dispositifs sélectionnés ne seront pas attribues et ne seront pas accessibles par le client.", + "unassign-devices-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 device} other {# devices}}?", + "unassign-from-customer": "Retirer du client", + "use-device-name-filter": "Utiliser le filtre", + "view-credentials": "Afficher les informations d'identification", + "view-devices": "Afficher les dispositifs" + }, + "dialog": { + "close": "Fermer le dialogue" + }, + "entity": { + "add-alias": "Ajouter un alias d'entité", + "alarm-name-starts-with": "Les actifs dont le nom commence par '{{prefix}}'", + "alias": "Alias", + "alias-required": "Un alias d'entité est requis.", + "aliases": "alias d'entité", + "all-subtypes": "Tout", + "any-entity": "Toute entité", + "asset-name-starts-with": "Les Assets dont le nom commence par '{{prefix}}'", + "columns-to-display": "Colonnes à afficher", + "configure-alias": "Configurer '{{alias}}' alias", + "create-new-alias": "Créez un nouveau!", + "create-new-key": "Créez un nouveau!", + "customer-name-starts-with": "Les clients dont les noms commencent par '{{prefix}}'", + "dashboard-name-starts-with": "Les tableaux de bord dont les noms commencent par '{{prefix}}'", + "details": "Détails de l'entité", + "device-name-starts-with": "Dispositifs dont le nom commence par '{{prefix}}'", + "duplicate-alias-error": "Alias ​​en double trouvé '{{alias}}'.
Les alias d'entité doivent être uniques dans le tableau de bord.", + "enter-entity-type": "Entrez le type d'entité", + "entities": "Entités", + "entity": "Entité", + "entity-alias": "Alias de l'entité", + "entity-list": "Liste d'entités", + "entity-list-empty": "Aucune entité sélectionnée.", + "entity-name": "Nom de l'entité", + "entity-name-filter-no-entity-matched": "Aucune entité commençant par '{{entity}}' n'a été trouvée.", + "entity-name-filter-required": "Le filtre de nom d'entité est requis.", + "entity-type": "Type d'entité", + "entity-type-list": "Liste de types d'entités", + "entity-type-list-empty": "Aucun type d'entité sélectionné.", + "entity-types": "Types d'entité", + "entity-view-name-starts-with": "Les vues d'entité dont le nom commence par '{{prefix}}'", + "key": "Clé", + "key-name": "Nom de la clé", + "list-of-alarms": "{count, plural, 1 {Une alarme} other {Liste de # alarmes}}", + "list-of-assets": "{count, plural, 1 {Un Asset} other {Liste de # Assets}}", + "list-of-customers": "{count, plural, 1 {Un client} other {Liste de # clients}}", + "list-of-dashboards": "{count, plural, 1 {Un tableau de bord} other {Liste de # tableaux de bord}}", + "list-of-devices": "{count, plural, 1 {Un dispositif} other {Liste de # dispositifs}}", + "list-of-plugins": "{count, plural, 1 {Un plugin} other {Liste de # plugins}}", + "list-of-rulechains": "{count, plural, 1 {Une chaîne de règles} other {Liste de # chaînes de règles}}", + "list-of-rulenodes": "{count, plural, 1 {Un noeud de règles} other {Liste de # noeuds de règles}}", + "list-of-rules": "{count, plural, 1 {Une règle} other {Liste de # règles}}", + "list-of-tenants": "{count, plural, 1 {Un tenant} other {Liste de # tenants}}", + "list-of-users": "{count, plural, 1 {Un utilisateur} other {Liste de # utilisateurs}}", + "missing-entity-filter-error": "Le filtre est manquant pour l'alias '{{alias}}'.", + "name-starts-with": "Nom commence par", + "no-alias-matching": "'{{alias}}' introuvable.", + "no-aliases-found": "Aucun alias trouvé.", + "no-data": "Aucune donnée à afficher", + "no-entities-matching": "Aucune entité correspondant à '{{entity}}' n'a été trouvée.", + "no-entities-prompt": "Aucune entité trouvée", + "no-entity-types-matching": "Aucun type d'entité correspondant à {{entityType}} n'a été trouvé. ", + "no-key-matching": "'{{key}}' introuvable.", + "no-keys-found": "Aucune clé trouvée", + "plugin-name-starts-with": "Plugins dont les noms commencent par '{{prefix}}'", + "remove-alias": "Supprimer l'alias d'entité", + "rule-name-starts-with": "Régles dont les noms commencent par '{{prefix}}'", + "rulechain-name-starts-with": "Chaînes de régles dont les noms commencent par '{{prefix}}'", + "rulenode-name-starts-with": "Les noeuds de régles dont le nom commence par '{{prefix}}'", + "search": "Recherche d'entités", + "select-entities": "Sélectionner des entités", + "selected-entities": "{count, plural, 1 {1 entité} other {# entités}} sélectionnées", + "tenant-name-starts-with": "Les Tenant dont le nom commence par '{{prefix}}'", + "type": "Type", + "type-alarm": "Alarme", + "type-alarms": "Alarmes", + "type-asset": "Actif", + "type-assets": "Actifs", + "type-current-customer": "Client actuel", + "type-customer": "Client", + "type-customers": "Clients", + "type-dashboard": "Tableau de bord", + "type-dashboards": "Tableaux de bord", + "type-device": "Dispositif", + "type-devices": "Dispositifs", + "type-entity-view": "Vue d'entité", + "type-entity-views": "Vues d'entités", + "type-plugin": "Plugin", + "type-plugins": "Plugins", + "type-required": "Le type d'entité est obligatoire.", + "type-rule": "Régle", + "type-rulechain": "Chaîne de régles", + "type-rulechains": "Chaînes de régles", + "type-rulenode": "Noeud de régle", + "type-rulenodes": "Noeuds de régle", + "type-rules": "Régles", + "type-tenant": "Tenant", + "type-tenants": "Tenants", + "type-user": "Utilisateur", + "type-users": "Utilisateurs", + "unable-delete-entity-alias-text": "L'alias d'entité '{{entityAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", + "unable-delete-entity-alias-title": "Impossible de supprimer l'alias d'entité", + "use-entity-name-filter": "Utiliser un filtre", + "user-name-starts-with": "Utilisateurs dont les noms commencent par '{{prefix}}'" + }, + "entity-field": { + "address": "Adresse", + "address2": "Adresse 2", + "city": "Ville", + "country": "Pays", + "created-time": "Heure de création", + "email": "Email", + "first-name": "Prénom", + "last-name": "Nom de famille", + "name": "Nom", + "phone": "Téléphone", + "state": "Prov", + "title": "Titre", + "type": "Type", + "zip": "Code postal" + }, + "entity-view": { + "add": "Ajouter une vue d'entité", + "add-alias": "Ajouter un alias de vue d'entité", + "add-entity-view-text": "Ajouter une nouvelle vue d'entité", + "alias": "Alias", + "alias-required": "Un alias de vue d'entité est requis.", + "aliases": "Alias de vue d'entité", + "any-entity-view": "Toute vue d'entité", + "assign-entity-view-to-customer": "Attribuer une (des) vue (s) d'entité à un client", + "assign-entity-view-to-customer-text": "Veuillez sélectionner les vues d'entité à affecter au client", + "assign-entity-views": "Attribuer des vues d'entité", + "assign-entity-views-text": "Attribuer { count, plural, 1 {1 entityView} other {# entityViews} } au client", + "assign-new-entity-view": "Attribuer une nouvelle vue d'entité", + "assign-to-customer": "Attribuer au client", + "assign-to-customer-text": "Veuillez sélectionner le client auquel attribuer la ou les vues d'entité.", + "assignedToCustomer": "Assigné au client", + "attributes-propagation": "Propagation des attributs", + "attributes-propagation-hint": "La vue d'entité copiera automatiquement les attributs spécifiés de l'entité cible chaque fois que vous enregistrez ou mettez à jour cette vue d'entité. Pour des raisons de performances, les attributs d'entité cible ne sont pas propagés à la vue d'entité à chaque changement d'attribut. Vous pouvez activer la propagation automatique en configurant le noeud de règle \" copier pour afficher \" dans votre chaîne de règles et en liant les messages \"Post attributs \" et \"attributs mis à jour \" au nouveau noeud de règle.", + "client-attributes": "Attributs du client", + "client-attributes-placeholder": "Attributs du client", + "configure-alias": "Configurez l'alias '{{alias}}'", + "copyId": "Copier l'ID de la vue d'entité", + "create-new-alias": "Créer un nouveau!", + "create-new-key": "Créer un nouveau!", + "date-limits": "Limites de date", + "delete": "Supprimer la vue d'entité", + "delete-entity-view-text": "Attention, après la confirmation, la vue de l'entité et toutes les données associées deviendront irrécupérables.", + "delete-entity-view-title": "Êtes-vous sûr de vouloir supprimer la vue de l'entité '{{entityViewName}}'?", + "delete-entity-views": "Supprimer les vues d'entité", + "delete-entity-views-action-title": "Supprimer { count, plural, 1 {1 entityView} other {# entityViews} }", + "delete-entity-views-text": "Attention, après la confirmation, toutes les vues d'entité sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", + "delete-entity-views-title": "Êtes-vous sûr de vouloir voir l'entité { count, plural, 1 {1 entityView} other {# entityViews} }?", + "description": "Description", + "details": "Détails", + "duplicate-alias-error": "Alias '{{alias}}' existe déjà.
Les alias de vue d'entité doivent être uniques dans le tableau de bord.", + "end-date": "Date de fin", + "end-ts": "Heure de fin", + "enter-entity-view-type": "Entrer le type de vue d'entité", + "entity-view": "Vue d'entité", + "entity-view-alias": "Alias de vue d'entité", + "entity-view-details": "Détails de la vue d'entité", + "entity-view-list": "Liste de vues d'entités", + "entity-view-list-empty": "Aucune vue d'entité sélectionnée.", + "entity-view-name-filter-no-entity-view-matched": "Aucune vue d'entité commençant par '{{entityView}}' n'a été trouvée.", + "entity-view-name-filter-required": "Un filtre de nom de vue d'entité est requis.", + "entity-view-required": "Une vue d'entité est requise.", + "entity-view-type": "Type de vue d'entité", + "entity-view-type-list-empty": "Aucun type de vue d'entité sélectionné.", + "entity-view-type-required": "Le type d'entité est requis.", + "entity-view-types": "Types de vues d'entité", + "entity-views": "Vues d'entité", + "events": "Événements", + "make-private": "Rendre la vue d'entité privée", + "make-private-entity-view-text": "Après la confirmation, la vue de l'entité et toutes ses données seront rendues privées et ne seront pas accessibles par d'autres", + "make-private-entity-view-title": "Êtes-vous sûr de vouloir rendre la vue d'entité '{{entityViewName}}' privée?", + "make-public": "Rendre la vue d'entité publique", + "make-public-entity-view-text": "Après la confirmation, la vue de l'entité et toutes ses données seront rendues publiques et accessibles à d'autres", + "make-public-entity-view-title": "Voulez-vous vraiment que la vue de l'entité '{{entityViewName}}' soit publique?", + "management": "Gestion de vue d'entité", + "name": "Nom", + "name-required": "Un nom est requis.", + "name-starts-with": "Le nom de la vue d'entité commence par", + "no-alias-matching": "'{{alias}}' non trouvé.", + "no-aliases-found": "Aucun alias trouvé.", + "no-entity-view-types-matching": "Aucun type de vue d'entité correspondant à '{{entitySubtype}}' n'a été trouvé.", + "no-entity-views-matching": "Aucune vue d'entité correspondant à '{{entity}}' n'a été trouvée.", + "no-entity-views-text": "Aucune vue d'entité trouvée.", + "no-key-matching": "'{{key}}' non trouvé.", + "no-keys-found": "Aucune clé trouvée.", + "remove-alias": "Supprimer un alias de vue d'entité", + "select-entity-view": "Sélectionner une vue d'entité", + "select-entity-view-type": "Sélectionner le type de vue d'entité", + "server-attributes": "Attributs du serveur", + "server-attributes-placeholder": "Attributs du serveur", + "shared-attributes": "Attributs partagés", + "shared-attributes-placeholder": "Attributs partagés", + "start-date": "Date de début", + "start-ts": "Heure de début", + "target-entity": "Entité cible", + "timeseries": "Séries chronologiques", + "timeseries-data": "Données de séries chronologiques", + "timeseries-data-hint": "Configurez les clés de données de séries chronologiques de l'entité cible qui seront accessibles à la vue de l'entité. Ces données temporelles sont en lecture seule.", + "timeseries-placeholder": "Séries chronologiques", + "unable-entity-view-device-alias-text": "L'alias de dispositif '{{entityViewAlias}}' ne peut pas être supprimé car il est utilisé par les widgets suivants:
{{widgetsList}}", + "unable-entity-view-device-alias-title": "Impossible de supprimer l'alias de la vue d'entité.", + "unassign-entity-view": "Annuler l'affectation de la vue d'entité", + "unassign-entity-view-text": "Après la confirmation, la vue de l'entité sera non attribuée et ne sera pas accessible par le client.", + "unassign-entity-view-title": "Voulez-vous vraiment annuler l'attribution de la vue d'entité '{{entityViewName}}'?", + "unassign-entity-views": "Annuler l'attribution des vues d'entité", + "unassign-entity-views-action-title": "Annuler l'attribution { count, plural, 1 {1 entityView} other {# entityViews} } du client", + "unassign-entity-views-text": "Après la confirmation, toutes les vues des entités sélectionnées seront non attribuées et ne seront pas accessibles par le client.", + "unassign-entity-views-title": "Êtes-vous sûr de vouloir annuler l'attribution { count, plural, 1 {1 entityView} other {# entityViews} }?", + "unassign-from-customer": "Annuler l'attribution au client", + "use-entity-view-name-filter": "Use filter", + "view-entity-views": "Voir les vues d'entité" + }, + "error": { + "unable-to-connect": "Impossible de se connecter au serveur! Veuillez vérifier votre connexion Internet.", + "unhandled-error-code": "Code d'erreur non géré: {{errorCode}}", + "unknown-error": "Erreur inconnue" + }, + "event": { + "alarm": "Alarme", + "body": "Corps", + "data": "Données", + "data-type": "Type de données", + "entity": "Entité", + "error": "erreur", + "errors-occurred": "Des erreurs sont survenues", + "event": "événement", + "event-time": "Heure de l'événement", + "event-type": "Type d'événement", + "failed": "Échec", + "message-id": "Message Id", + "message-type": "Type de message", + "messages-processed": "Messages traités", + "metadata": "Métadonnées", + "method": "Méthode", + "no-events-prompt": "Aucun événement trouvé", + "relation-type": "Type de relation", + "server": "Serveur", + "status": "État", + "success": "Succès", + "type": "Type", + "type-debug-rule-chain": "Debug", + "type-debug-rule-node": "Debug", + "type-error": "Erreur", + "type-lc-event": "Evénement du cycle de vie", + "type-stats": "Statistiques" + }, + "extension": { + "add": "Ajouter une extension", + "add-attribute": "Ajouter un attribut", + "add-attribute-request": "Ajouter une demande d'attribut", + "add-attribute-update": "Ajouter une mise à jour d'attribut", + "add-broker": "Ajouter un Broker", + "add-config": "Ajouter une configuration de convertisseur", + "add-connect-request": "Ajouter une demande de connexion", + "add-converter": "Ajouter un convertisseur", + "add-device": "Ajouter un dispositif", + "add-disconnect-request": "Ajouter une demande de déconnexion", + "add-map": "Ajouter un élément de mappage", + "add-server-side-rpc-request": "Ajouter une requête RPC côté serveur", + "add-timeseries": "Ajouter des timeseries", + "anonymous": "Anonyme", + "attr-json-key-expression": "Expression json de la clé d'attribut", + "attr-topic-key-expression": "Expression du topic de la clé d'attribut", + "attribute-filter": "Filtre d'attribut", + "attribute-key-expression": "Expression de clé d'attribut", + "attribute-requests": "Demandes d'attributs", + "attribute-updates": "Mises à jour des attributs", + "attributes": "Attributs", + "basic": "Basic", + "brokers": "Brokers", + "ca-cert": "Fichier de certificat CA", + "cert": "Fichier de certificat *", + "client-scope": "Portée client", + "configuration": "Configuration", + "connect-requests": "Demandes de connexion", + "converter-configurations": "Configurations du convertisseur", + "converter-id": "ID du convertisseur", + "converter-json": "Json", + "converter-json-parse": "Impossible d'analyser le convertisseur json.", + "converter-json-required": "Le convertisseur json est requis.", + "converter-type": "Type de convertisseur", + "converters": "Convertisseurs", + "credentials": "Informations d'identification", + "custom": "Sur mesure", + "delete": "Supprimer l'extension", + "delete-extension-text": "Attention, après la confirmation, l'extension et toutes les données associées deviendront irrécupérables.", + "delete-extension-title": "Êtes-vous sûr de vouloir supprimer l'extension '{{extensionId}}'?", + "delete-extensions-text": "Attention, après la confirmation, toutes les extensions sélectionnées seront supprimées.", + "delete-extensions-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 extension} other {# extensions}}?", + "device-name-expression": "expression du nom du dispositif", + "device-name-filter": "Filtre de nom de dispositif", + "device-type-expression": "expression de type de dispositif", + "disconnect-requests": "Demandes de déconnection", + "drop-file": "Déposez un fichier ou cliquez pour sélectionner un fichier à télécharger.", + "edit": "Modifier l'extension", + "export-extension": "Exporter l'extension", + "export-extensions-configuration": "Exporter la configuration des extensions", + "extension-id": "Id de l'extension", + "extension-type": "Type d'extension", + "extensions": "Extensions", + "field-required": "Le champ est obligatoire", + "file": "Fichier d'extensions", + "filter-expression": "Expression du filtre", + "host": "Hôte", + "id": "Id", + "import-extension": "Importer une extension", + "import-extensions": "Importer des extensions", + "import-extensions-configuration": "Importer la configuration des extensions", + "invalid-file-error": "Fichier d'extension non valide", + "json-name-expression": "Expression json du nom du dispositif", + "json-parse": "Impossible d'analyser json transformer.", + "json-required": "Transformer json est requis.", + "json-type-expression": "Expression json du type de dispositif", + "key": "Clé", + "mapping": "Mappage", + "method-filter": "Filtre de méthode", + "modbus-add-server": "Ajouter serveur/esclave", + "modbus-add-server-prompt": "Veuillez ajouter serveur/esclave", + "modbus-attributes-poll-period": "Période d'interrogation des attributs (ms)", + "modbus-baudrate": "Débit en bauds", + "modbus-byte-order": "Ordre des octets", + "modbus-databits": "Bits de données", + "modbus-databits-range": "Les bits de données doivent être compris entre 7 et 8.", + "modbus-device-name": "Nom du dispositif", + "modbus-encoding": "Encodage", + "modbus-function": "Fonction", + "modbus-parity": "parité", + "modbus-poll-period": "Période d'interrogation (ms)", + "modbus-poll-period-range": "La période d'interrogation doit être une valeur positive.", + "modbus-port-name": "Nom du port série", + "modbus-register-address": "Adresse du registre", + "modbus-register-address-range": "L'adresse du registre doit être comprise entre 0 et 65535.", + "modbus-register-bit-index": "Bit index", + "modbus-register-bit-index-range": "L'index de bit doit être compris entre 0 et 15.", + "modbus-register-count": "Nombre de registre", + "modbus-register-count-range": "Le nombre de registres doit être une valeur positive.", + "modbus-server": "Serveurs / esclaves", + "modbus-stopbits": "Bits d'arrêt", + "modbus-stopbits-range": "Les bits d'arrêt doivent être compris entre 1 et 2.", + "modbus-tag": "Tag", + "modbus-timeseries-poll-period": "Période d'interrogation des Timeseries (ms)", + "modbus-transport": "Transport", + "modbus-unit-id": "Id de l'unité", + "modbus-unit-id-range": "L'ID de l'unité doit être compris entre 1 et 247.", + "no-file": "Aucun fichier sélectionné.", + "opc-add-server": "Ajouter un serveur", + "opc-add-server-prompt": "Veuillez ajouter un serveur", + "opc-application-name": "Nom de l'application", + "opc-application-uri": "Uri de l'application", + "opc-device-name-pattern": "modèle de nom du dispositif", + "opc-device-node-pattern": "modèle de noeud de dispositif", + "opc-identity": "Identité", + "opc-keystore": "Magasin de clés", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Mot de passe de la clé", + "opc-keystore-location": "Emplacement *", + "opc-keystore-password": "Mot de passe", + "opc-keystore-type": "Type", + "opc-scan-period-in-seconds": "Période d'analyse en secondes", + "opc-security": "Sécurité", + "opc-server": "Serveurs", + "opc-type": "Type", + "password": "Mot de passe", + "pem": "PEM", + "port": "Port", + "port-range": "Le port doit être compris entre 1 et 65535.", + "private-key": "Fichier de clé privée *", + "request-id-expression": "Expression de demande d'id", + "request-id-json-expression": "Expression json de la demande d'id", + "request-id-topic-expression": "Expression de la demande d'id du topic", + "request-topic-expression": "Expression de la demande du topic", + "response-timeout": "Délai de réponse en millisecondes", + "response-topic-expression": "Expression du topic de la réponse", + "retry-interval": "Intervalle de nouvelle tentative en millisecondes", + "selected-extensions": "{count, plural, 1 {1 extension} other {# extensions}} sélectionné", + "server-side-rpc": "RPC côté serveur", + "ssl": "Ssl", + "sync": { + "last-sync-time": "Dernière heure de synchronisation", + "not-available": "Non disponible", + "not-sync": "Non sync", + "status": "Status", + "sync": "Sync" + }, + "timeout": "Délai d'attente en millisecondes", + "timeseries": "Timeseries", + "to-double": "Au double", + "token": "Jeton de sécurité", + "topic": "Topic", + "topic-expression": "Expression du topic", + "topic-filter": "Filtre du topic", + "topic-name-expression": "Expression du nom du dispositif (topic)", + "topic-type-expression": "Expression de type de dispositif (topic)", + "transformer": "Transformer", + "transformer-json": "JSON *", + "type": "Type", + "unique-id-required": "L'identifiant d'extension actuel existe déjà.", + "username": "Nom d'utilisateur", + "value": "Valeur", + "value-expression": "Expression de la valeur" + }, + "fullscreen": { + "exit": "Quitter le plein écran", + "expand": "Afficher en plein écran", + "fullscreen": "Plein écran", + "toggle": "Activer le mode plein écran" + }, + "function": { + "function": "Fonction" + }, + "grid": { + "add-item-text": "Ajouter un nouvel élément", + "delete-item": "Supprimer l'élément", + "delete-item-text": "Faites attention, après la confirmation, cet élément et toutes les données associées deviendront irrécupérables.", + "delete-item-title": "Êtes-vous sûr de vouloir supprimer cet élément?", + "delete-items": "Supprimer les éléments", + "delete-items-action-title": "Supprimer {count, plural, 1 {1 élément} other {# éléments}}", + "delete-items-text": "Attention, après la confirmation, tous les éléments sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-items-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 élément} other {# éléments}}?", + "item-details": "Détails de l'élément", + "no-items-text": "Aucun élément trouvé", + "scroll-to-top": "Défiler vers le haut" + }, + "help": { + "goto-help-page": "Aller à la page d'aide" + }, + "home": { + "avatar": "Avatar", + "home": "Accueil", + "logout": "Déconnexion", + "menu": "Menu", + "open-user-menu": "Ouvrir le menu utilisateur", + "profile": "Profile" + }, + "icon": { + "icon": "Icône", + "material-icons": "Icônes matérielles", + "select-icon": "Sélectionner l'icône", + "show-all": "Afficher toutes les icônes" + }, + "import": { + "drop-file": "Déposez un fichier JSON ou cliquez pour sélectionner un fichier à télécharger.", + "no-file": "Aucun fichier sélectionné" + }, + "item": { + "selected": "Sélectionné" + }, + "js-func": { + "no-return-error": "La fonction doit renvoyer une valeur!", + "return-type-mismatch": "La fonction doit renvoyer une valeur de type '{{type}}' !", + "tidy": "Nettoyer" + }, + "key-val": { + "add-entry": "Ajouter une entrée", + "key": "Clé", + "no-data": "Aucune entrée", + "remove-entry": "Supprimer l'entrée", + "value": "Valeur" + }, + "language": { + "language": "Language", + "locales": { + "de_DE": "Allemand", + "en_US": "Anglais", + "fr_FR": "Français", + "es_ES": "Espagnol", + "it_IT": "Italien", + "ko_KR": "Coréen", + "ru_RU": "Russe", + "zh_CN": "Chinois", + "ja_JA": "Japonaise", + "tr_TR": "Turc", + "fa_IR": "Persane", + "uk_UA": "Ukrainien", + "cs_CZ": "Tchèque", + "el_GR": "Grec", + "lv_LV": "Letton" + } + }, + "layout": { + "color": "Couleur", + "layout": "Mise en page", + "main": "Principal", + "manage": "Gérer les mises en page", + "right": "Droite", + "select": "Sélectionner la mise en page cible", + "settings": "Paramètres de mise en page" + }, + "legend": { + "avg": "moy", + "max": "max", + "min": "min", + "position": "Position de la légende", + "settings": "Paramètres de la légende", + "show-avg": "Afficher la valeur moyenne", + "show-max": "Afficher la valeur maximale", + "show-min": "Afficher la valeur min", + "show-total": "Afficher la valeur totale", + "total": "total" + }, + "login": { + "create-password": "Créer un mot de passe", + "email": "Email", + "forgot-password": "Mot de passe oublié?", + "login": "Login", + "new-password": "Nouveau mot de passe", + "new-password-again": "nouveau mot de passe", + "password-again": "Mot de passe à nouveau", + "password-link-sent-message": "Le lien de réinitialisation du mot de passe a été envoyé avec succès!", + "password-reset": "Mot de passe réinitialisé", + "passwords-mismatch-error": "Les mots de passe saisis doivent être identiques!", + "remember-me": "Se souvenir de moi", + "request-password-reset": "Demander la réinitialisation du mot de passe", + "reset-password": "Réinitialiser le mot de passe", + "sign-in": "Veuillez vous connecter", + "username": "Nom d'utilisateur (courriel)" + }, + "position": { + "bottom": "Bas", + "left": "Gauche", + "right": "Droite", + "top": "Haut" + }, + "profile": { + "change-password": "Modifier le mot de passe", + "current-password": "Mot de passe actuel", + "last-login-time": "Dernière connexion", + "profile": "Profile" + }, + "relation": { + "add": "Ajouter une relation", + "add-relation-filter": "Ajouter un filtre de relation", + "additional-info": "Informations supplémentaires (JSON)", + "any-relation": "toute relation", + "any-relation-type": "N'importe quel type", + "delete": "Supprimer la relation", + "delete-from-relation-text": "Attention, après la confirmation, l'entité actuelle ne sera pas liée à l'entité '{{entityName}}'.", + "delete-from-relation-title": "Êtes-vous sûr de vouloir supprimer la relation de l'entité '{{entityName}}'?", + "delete-from-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et l'entité actuelle ne sera pas liée aux entités correspondantes.", + "delete-from-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "delete-to-relation-text": "Attention, après la confirmation, l'entité '{{entityName}} ne sera plus liée à l'entité actuelle.", + "delete-to-relation-title": "Êtes-vous sûr de vouloir supprimer la relation avec l'entité '{{entityName}}'?", + "delete-to-relations-text": "Attention, après la confirmation, toutes les relations sélectionnées seront supprimées et les entités correspondantes ne seront pas liées à l'entité en cours.", + "delete-to-relations-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 relation} other {# relations}}?", + "direction": "Sens", + "direction-type": { + "FROM": "de", + "TO": "à" + }, + "edit": "Modifier la relation", + "from-entity": "De l'entité", + "from-entity-name": "Du nom d'entité", + "from-entity-type": "Du type d'entité", + "from-relations": "Relations sortantes", + "invalid-additional-info": "Impossible d'analyser les informations supplémentaires json.", + "relation-filters": "Filtres de relation", + "relation-type": "Type de relation", + "relation-type-required": "Le type de relation est requis.", + "relations": "Relations", + "remove-relation-filter": "Supprimer le filtre de relation", + "search-direction": { + "FROM": "De", + "TO": "Vers" + }, + "selected-relations": "{count, plural, 1 {1 relation} other {# relations}} sélectionné", + "to-entity": "Vers l'entité", + "to-entity-name": "vers le nom de l'entité", + "to-entity-type": "Vers le type d'entité", + "to-relations": "Relations entrantes", + "type": "Type" + }, + "rulechain": { + "add": "Ajouter une chaîne de règles", + "add-rulechain-text": "Ajouter une nouvelle chaîne de règles", + "copyId": "Copier l'identifiant de la chaîne de règles", + "create-new-rulechain": "Créer une nouvelle chaîne de règles", + "debug-mode": "Mode de débogage", + "delete": "Supprimer la chaîne de règles", + "delete-rulechain-text": "Attention, après la confirmation, la chaîne de règles et toutes les données associées deviendront irrécupérables.", + "delete-rulechain-title": "Voulez-vous vraiment supprimer la chaîne de règles '{{ruleChainName}}'?", + "delete-rulechains-action-title": "Supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}", + "delete-rulechains-text": "Attention, après la confirmation, toutes les chaînes de règles sélectionnées seront supprimées et toutes les données associées deviendront irrécupérables.", + "delete-rulechains-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}}?", + "description": "Description", + "details": "Détails", + "events": "Evénements", + "export": "Exporter la chaîne de règles", + "export-failed-error": "Impossible d'exporter la chaîne de règles: {{error}}", + "idCopiedMessage": "L'ID de la chaîne de règles a été copié dans le presse-papier", + "import": "Importer la chaîne de règles", + "invalid-rulechain-file-error": "Impossible d'importer la chaîne de règles: structure de données de la chaîne de règles non valide", + "management": "Gestion des règles", + "name": "Nom", + "name-required": "Le nom est requis.", + "no-rulechains-matching": "Aucune chaîne de règles correspondant à {{entity}} n'a été trouvée.", + "no-rulechains-text": "Aucune chaîne de règles trouvée", + "root": "Racine", + "rulechain": "Chaîne de règles", + "rulechain-details": "Détails de la chaîne de règles", + "rulechain-file": "Fichier de chaîne de règles", + "rulechain-required": "Chaîne de règles requise", + "rulechains": "Chaînes de règles", + "select-rulechain": "Sélectionner la chaîne de règles", + "set-root": "Rend la chaîne de règles racine (root) ", + "set-root-rulechain-text": "Après la confirmation, la chaîne de règles deviendra racine (root) et gérera tous les messages de transport entrants.", + "set-root-rulechain-title": "Voulez-vous vraiment que la chaîne de règles '{{ruleChainName}} soit racine (root) ?", + "system": "Système" + }, + "rulenode": { + "add": "Ajouter un noeud de règle", + "add-link": "Ajouter un lien", + "configuration": "Configuration", + "copy-selected": "Copier les éléments sélectionnés", + "create-new-link-label": "Créez un nouveau!", + "custom-link-label": "Etiquette de lien personnalisée", + "custom-link-label-required": "Une étiquette de lien personnalisée est requise", + "debug-mode": "Mode de débogage", + "delete": "Supprimer le noeud de règle", + "delete-selected": "Supprimer les éléments sélectionnés", + "delete-selected-objects": "Supprimer les nœuds et les connexions sélectionnés", + "description": "Description", + "deselect-all": "Désélectionner tout", + "deselect-all-objects": "Désélectionnez tous les nœuds et toutes les connexions", + "details": "Détails", + "directive-is-not-loaded": "La directive de configuration définie '{{directiveName}} n'est pas disponible.", + "events": "Événements", + "help": "Aide", + "invalid-target-rulechain": "Impossible de résoudre la chaîne de règles cible!", + "link": "Lien", + "link-details": "Détails du lien du noeud de la règle", + "link-label": "Étiquette du lien", + "link-label-required": "L'étiquette du lien est obligatoire", + "link-labels": "Étiquettes de lien", + "link-labels-required": "Les étiquettes de lien sont obligatoires", + "message": "Message", + "message-type": "Type de message", + "message-type-required": "Le type de message est obligatoire", + "metadata": "Métadonnées", + "metadata-required": "Les entrées de métadonnées ne peuvent pas être vides.", + "name": "Nom", + "name-required": "Le nom est requis.", + "no-link-label-matching": "'{{label}}' introuvable.", + "no-link-labels-found": "Aucune étiquette de lien trouvée", + "open-node-library": "Ouvrir la bibliothèque de noeud", + "output": "Output", + "rulenode-details": "Détails du noeud de la régle", + "search": "Recherche de noeuds", + "select-all": "Tout sélectionner", + "select-all-objects": "Sélectionnez tous les noeuds et connexions", + "select-message-type": "Sélectionner le type de message", + "test": "Test", + "test-script-function": "Tester le script", + "type": "Type", + "type-action": "Action", + "type-action-details": "Effectuer une action spéciale", + "type-enrichment": "Enrichissement", + "type-enrichment-details": "Ajouter des informations supplémentaires dans les métadonnées de message", + "type-external": "Externe", + "type-external-details": "Interagit avec le systéme externe", + "type-filter": "Filtre", + "type-filter-details": "Filtrer les messages entrants avec des conditions configurées", + "type-input": "Input", + "type-input-details": "Entrée logique de la chaîne de règles, transmet les messages entrants au prochain nœud de règle associé", + "type-rule-chain": "Chaîne de régles", + "type-rule-chain-details": "Transmet les messages entrants à la chaîne de régles spécifiée", + "type-transformation": "Transformation", + "type-transformation-details": "Modifier le payload du message et les métadonnées ", + "type-unknown": "Inconnu", + "type-unknown-details": "Noeud de règle non résolu", + "ui-resources-load-error": "Impossible de charger les ressources de configuration de l'interface utilisateur." + }, + "tenant": { + "add": "Ajouter un Tenant", + "add-tenant-text": "Ajouter un nouveau Tenant", + "admins": "Admins", + "copyId": "Copier l'Id du Tenant", + "delete": "Supprimer le Tenant", + "delete-tenant-text": "Attention, après la confirmation, le Tenant et toutes les données associées deviendront irrécupérables.", + "delete-tenant-title": "Êtes-vous sûr de vouloir supprimer le tenant '{{tenantTitle}}'?", + "delete-tenants-action-title": "Supprimer {count, plural, 1 {1 tenant} other {# tenants}}", + "delete-tenants-text": "Attention, après la confirmation, tous les Tenants sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-tenants-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 tenant} other {# tenants}}?", + "description": "Description", + "details": "Détails", + "events": "Événements", + "idCopiedMessage": "L'Id du Tenant a été copié dans le Presse-papiers", + "manage-tenant-admins": "Gérer les administrateurs du Tenant", + "management": "Gestion des Tenants", + "no-tenants-matching": "Aucun Tenant correspondant à {{entity}} n'a été trouvé. ", + "no-tenants-text": "Aucun Tenant trouvé", + "select-tenant": "Sélectionner un Tenant", + "tenant": "Tenant", + "tenant-details": "Détails du Tenant", + "tenant-required": "Tenant requis", + "tenants": "Tenants", + "title": "Titre", + "title-required": "Le titre est requis." + }, + "timeinterval": { + "advanced": "Avancé", + "days": "Jours", + "days-interval": "{days, plural, 1 {1 jour} other {# jours}}", + "hours": "Heures", + "hours-interval": "{hours, plural, 1 {1 heure} other {# heures}}", + "minutes": "Minutes", + "minutes-interval": "{minutes, plural, 1 {1 minute} other {# minutes}}", + "seconds": "Secondes", + "seconds-interval": "{seconds, plural, 1 {1 seconde} other {# secondes}}" + }, + "timewindow": { + "date-range": "Plage de dates", + "days": "{days, plural, 1 {jour} other {# jours}}", + "edit": "Modifier timewindow", + "history": "Historique", + "hours": "{hours, plural, 0 {heure} 1 {1 heure} other {# heures}}", + "last": "Dernier", + "last-prefix": "dernier", + "minutes": "{minutes, plural, 0 {minute} 1 {1 minute} other {# minutes}}", + "period": "de {{startTime}} à {{endTime}}", + "realtime": "Temps réel", + "seconds": "{seconds, plural, 0 {second} 1 {1 second} other {# seconds}}", + "time-period": "Période", + "hide": "Masquer" + }, + "user": { + "activation-email-sent-message": "Le courriel d'activation a été envoyé avec succès!", + "activation-link": "Lien d'activation utilisateur", + "activation-link-copied-message": "le lien d'activation de l'utilisateur a été copié dans le presse-papier", + "activation-link-text": "Pour activer l'utilisateur, utilisez le lien d'activation suivant: ", + "activation-method": "Méthode d'activation", + "add": "Ajouter un utilisateur", + "add-user-text": "Ajouter un nouvel utilisateur", + "always-fullscreen": "Toujours en plein écran", + "anonymous": "Anonyme", + "copy-activation-link": "Copier le lien d'activation", + "customer": "Client", + "customer-users": "Utilisateurs du client", + "default-dashboard": "Tableau de bord par défaut", + "delete": "Supprimer l'utilisateur", + "delete-user-text": "Attention, après la confirmation, l'utilisateur et toutes les données associées deviendront irrécupérables.", + "delete-user-title": "Êtes-vous sûr de vouloir supprimer l'utilisateur '{{userEmail}}'?", + "delete-users-action-title": "Supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}", + "delete-users-text": "Attention, après la confirmation, tous les utilisateurs sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-users-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 utilisateur} other {# utilisateurs}}?", + "description": "Description", + "details": "Détails", + "disable-account": "Désactiver le compte d'utilisateur", + "disable-account-message": "Le compte d'utilisateur a été désactivé avec succès!", + "display-activation-link": "Afficher le lien d'activation", + "email": "Email", + "email-required": "Email est requis.", + "enable-account": "Activer le compte d'utilisateur", + "enable-account-message": "Le compte d'utilisateur a été activé avec succès!", + "first-name": "Prénom", + "invalid-email-format": "Format de courrier électronique non valide", + "last-name": "Nom de famille", + "login-as-customer-user": "Se connecter en tant qu'utilisateur client", + "login-as-tenant-admin": "Connectez-vous en tant qu'administrateur Tenant", + "no-users-matching": "Aucun utilisateur correspondant à '{{entity}}' n'a été trouvé.", + "no-users-text": "Aucun utilisateur trouvé", + "resend-activation": "Renvoyer l'activation", + "select-user": "Sélectionner l'utilisateur", + "send-activation-mail": "Envoyer un mail d'activation", + "sys-admin": "Administrateur du système", + "tenant-admin": "Administrateur du Tenant", + "tenant-admins": "administrateurs du Tenant", + "user": "utilisateur", + "user-details": "Détails de l'utilisateur", + "user-required": "L'utilisateur est requis", + "users": "Utilisateurs" + }, + "value": { + "boolean": "booléen", + "boolean-value": "Valeur booléenne", + "double": "Double", + "double-value": "Valeur double", + "false": "Faux", + "integer": "Entier", + "integer-value": "Valeur entière", + "invalid-integer-value": "Valeur entière invalide", + "long": "Long", + "string": "String", + "string-value": "Valeur String", + "true": "Vrai", + "type": "Type de valeur" + }, + "widget": { + "add": "Ajouter un widget", + "add-resource": "Ajouter une ressource", + "add-widget-type": "Ajouter un nouveau type de widget", + "alarm": "Widget d'alarme", + "css": "CSS", + "datakey-settings-schema": "Schéma des paramètres de Data key", + "edit": "Modifier le widget", + "editor": " Editeur de widget", + "export": "Exporter widget", + "html": "HTML", + "javascript": "Javascript", + "latest-values": "Dernières valeurs", + "management": "Gestion des widgets", + "missing-widget-title-error": "Le titre du widget doit être spécifié!", + "no-data-found": "Aucune donnée trouvée", + "remove": "Supprimer le widget", + "remove-resource": "Supprimer une ressource", + "remove-widget-text": "Après la confirmation, le widget et toutes les données associées deviendront irrécupérables.", + "remove-widget-title": "Êtes-vous sûr de vouloir supprimer le widget '{{widgetTitle}}'?", + "remove-widget-type": "Supprimer le type de widget", + "remove-widget-type-text": "Après la confirmation, le type de widget et toutes les données associées deviendront irrécupérables.", + "remove-widget-type-title": "Êtes-vous sûr de vouloir supprimer le type de widget '{{widgetName}}'?", + "resource-url": "URL JavaScript / CSS", + "resources": "Ressources", + "rpc": "Widget de contrôle", + "run": "Exécuter un widget", + "save": "Enregistrer le widget", + "save-widget-type-as": "Enregistrer le type de widget sous", + "save-widget-type-as-text": "Veuillez saisir un nouveau titre de widget et / ou sélectionner un ensemble de widgets cibles", + "saveAs": "Enregistrer le widget sous", + "search-data": "Rechercher des données", + "select-widget-type": "Sélectionnez le type de widget", + "select-widgets-bundle": "Sélectionner un ensemble de widgets", + "settings-schema": "Schéma des paramétres", + "static": "Widget statique", + "tidy": "Nettoyer", + "timeseries": "Séries chronologiques", + "title": "Titre du widget", + "title-required": "Le titre du widget est requis.", + "toggle-fullscreen": "Basculer le mode plein écran", + "type": "Type de widget", + "unable-to-save-widget-error": "Impossible de sauvegarder le widget! Le widget a des erreurs!", + "undo": "Annuler les modifications du widget", + "widget-bundle": "Ensemble de widget", + "widget-library": "Bibliothèque de widgets", + "widget-saved": "Widget enregistré", + "widget-template-load-failed-error": "Impossible de charger le modéle de widget!", + "widget-type-load-error": "Le widget n'a pas été chargé à cause des erreurs suivantes:", + "widget-type-load-failed-error": "Impossible de charger le type de widget!", + "widget-type-not-found": "Problème de chargement de la configuration du widget.
Le type de widget associé a probablement été supprimé." + }, + "widget-action": { + "custom": "Action personnalisée", + "header-button": "Bouton d'en-tête de widget", + "open-dashboard": "Naviguer vers un autre tableau de bord", + "open-dashboard-state": "Naviguer vers un nouvel état du tableau de bord", + "open-right-layout": "Ouvrir la disposition du tableau de bord droite (vue mobile)", + "set-entity-from-widget": "Définir l'entité à partir du widget", + "target-dashboard": "Tableau de bord cible", + "target-dashboard-state": "État du tableau de bord cible", + "target-dashboard-state-required": "L'état du tableau de bord cible est requis", + "update-dashboard-state": "Mettre à jour l'état actuel du tableau de bord" + }, + "widget-config": { + "action": "Action", + "action-icon": "Icône", + "action-name": "Nom", + "action-name-not-unique": "Une autre action portant le même nom existe déjà.
Le nom de l'action doit être unique dans la même source d'action.", + "action-name-required": "Le nom de l'action est requis", + "action-source": "Source de l'action", + "action-source-required": "Une source d'action est requise.", + "action-type": "Type", + "action-type-required": "Le type d'action est requis.", + "actions": "Actions", + "add-action": "Ajouter une action", + "add-datasource": "Ajouter une source de données", + "advanced": "Avancé", + "alarm-source": "Source d'alarme", + "background-color": "couleur de fond", + "data": "Données", + "datasource-parameters": "Paramètres", + "datasource-type": "Type", + "datasources": "Sources de données", + "decimals": "Nombre de chiffres après virgule flottante", + "delete-action": "Supprimer l'action", + "delete-action-text": "Êtes-vous sûr de vouloir supprimer l'action du widget nommé '{{actionName}}'?", + "delete-action-title": "Supprimer l'action du widget", + "display-timewindow": "Afficher fenêtre de temps", + "display-legend": "Afficher la légende", + "display-title": "Afficher le titre", + "drop-shadow": "Ombre portée", + "edit-action": "Modifier l'action", + "enable-fullscreen": "Activer le plein écran", + "general-settings": "Paramètres généraux", + "height": "Hauteur", + "margin": "Marge", + "maximum-datasources": "Maximum {count, plural, 1 {1 datasource est autorisé.} other {# datasources sont autorisés}}", + "mobile-mode-settings": "Paramètres du mode mobile", + "order": "Ordre", + "padding": "Padding", + "remove-datasource": "Supprimer la source de données", + "search-actions": "Recherche d'actions", + "settings": "Paramètres", + "target-device": "Dispositif cible", + "text-color": "Couleur du texte", + "timewindow": "Fenêtre de temps", + "title": "Titre", + "title-style": "Style de titre", + "title-tooltip": "Tooltip de titre", + "units": "Symbole spécial à afficher à côté de la valeur", + "use-dashboard-timewindow": "Utiliser la fenêtre de temps du tableau de bord", + "widget-style": "Style du widget", + "display-icon": "Afficher l'icône du titre", + "icon-color": "Couleur de l'icône", + "icon-size": "Taille de l'icône" + }, + "widget-type": { + "create-new-widget-type": "Créer un nouveau type de widget", + "export": "Exporter le type de widget", + "export-failed-error": "Impossible d'exporter le type de widget: {{error}}", + "import": "Importer le type de widget", + "invalid-widget-type-file-error": "Impossible d'importer le type de widget: structure de données de type widget invalide.", + "widget-type-file": "Fichier de type Widget" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dim.", + "Mon": "Lun.", + "Tue": "Mar.", + "Wed": "Mer.", + "Thu": "Jeu.", + "Fri": "Ven.", + "Sat": "Sam.", + "Jan": "Janv.", + "Feb": "Févr.", + "Mar": "Mars", + "Apr": "Avr.", + "May": "Mai", + "Jun": "Juin", + "Jul": "Juil.", + "Aug": "Août", + "Sep": "Sept.", + "Oct": "Oct.", + "Nov": "Nov.", + "Dec": "Déc.", + "January": "Janvier", + "February": "Février", + "March": "Mars", + "April": "Avril", + "June": "Juin", + "July": "Juillet", + "August": "Août", + "September": "Septembre", + "October": "Octobre", + "November": "Novembre", + "December": "Décembre", + "Custom Date Range": "Plage de dates personnalisée", + "Date Range Template": "Modèle de plage de dates", + "Today": "Aujourd'hui", + "Yesterday": "Hier", + "This Week": "Cette semaine", + "Last Week": "La semaine dernière", + "This Month": "Ce mois-ci", + "Last Month": "Le mois dernier", + "Year": "Année", + "This Year": "Cette année", + "Last Year": "L'année dernière", + "Date picker": "Sélecteur de date", + "Hour": "Heure", + "Day": "Journée", + "Week": "La semaine", + "2 weeks": "2 Semaines", + "Month": "Mois", + "3 months": "3 Mois", + "6 months": "6 Mois", + "Custom interval": "Intervalle personnalisé", + "Interval": "Intervalle", + "Step size": "Taille de pas", + "Ok": "Ok" + } + }, + "input-widgets": { + "attribute-not-allowed": "Le paramètre d'attribut ne peut pas être utilisé dans ce widget", + "date": "Date", + "discard-changes": "Annuler les modifications", + "entity-attribute-required": "L'attribut d'entité est requis", + "entity-timeseries-required": "Entité timeseries est requis", + "not-allowed-entity": "L'entité sélectionnée ne peut pas avoir d'attributs partagés", + "no-attribute-selected": "Aucun attribut n'est sélectionné", + "no-datakey-selected": "Aucune date n'est sélectionnée", + "no-entity-selected": "Aucune entité sélectionnée", + "no-image": "Pas d'image", + "no-support-web-camera": "Pas de webcam supportée", + "no-timeseries-selected": "Aucune série temporelle sélectionnée", + "switch-attribute-value": "Changer la valeur de l'attribut d'entité", + "switch-camera": "Changer de caméra", + "switch-timeseries-value": "Changer la valeur de l'entité série temporelle", + "take-photo": "Prendre une photo", + "time": "Temps", + "timeseries-not-allowed": "Le paramètre série temporelle ne peut pas être utilisé dans ce widget", + "update-failed": "Mise à jour a échoué", + "update-successful": "Mise à jour réussie", + "update-attribute": "Attribut de mise à jour", + "update-timeseries": "Mise à jour de la série temporelle", + "value": "Valeur" + } + }, + "widgets-bundle": { + "add": "Ajouter un groupe de widgets", + "add-widgets-bundle-text": "Ajouter un nouveau groupe de widgets", + "create-new-widgets-bundle": "Créer un nouveau groupe de widgets", + "current": "Groupe actuel", + "delete": "Supprimer le groupe de widgets", + "delete-widgets-bundle-text": "Attention, après la confirmation, le groupe de widgets et toutes les données associées deviendront irrécupérables.", + "delete-widgets-bundle-title": "Êtes-vous sûr de vouloir supprimer le groupe de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundles-action-title": "Supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}", + "delete-widgets-bundles-text": "Attention, après la confirmation, tous les groupes de widgets sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", + "delete-widgets-bundles-title": "Voulez-vous vraiment supprimer {count, plural, 1 {1 groupe de widgets} other {# groupes de widgets}}?", + "details": "Détails", + "empty": "Le groupe de widgets est vide", + "export": "Exporter le groupe de widgets", + "export-failed-error": "Impossible d'exporter le groupe de widgets: {{error}}", + "import": "Importer un groupe de widgets", + "invalid-widgets-bundle-file-error": "Impossible d'importer un groupe de widgets: structure de données du groupe de widgets non valides.", + "no-widgets-bundles-matching": "Aucun groupe de widgets correspondant à {{widgetsBundle}} n'a été trouvé.", + "no-widgets-bundles-text": "Aucun groupe de widgets trouvé", + "system": "Système", + "title": "Titre", + "title-required": "Le titre est requis.", + "widgets-bundle-details": "Détails des groupes de widgets", + "widgets-bundle-file": "Fichier de groupe de widgets", + "widgets-bundle-required": "Un groupe de widgets est requis.", + "widgets-bundles": "Groupes de widgets" + } +} diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 1c65d04096..8b53c91983 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -84,6 +84,8 @@ "timeout-required": "Timeout obbligatorio.", "timeout-invalid": "Timeout non valido.", "enable-tls": "Abilita TLS", + "tls-version" : "Versione TLS", + "enter-tls-version" : "Inserisci la versione TLS", "send-test-mail": "Invia mail di test", "security-settings": "Settaggi di sicurezza", "password-policy": "Politica password", diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json index e1ebd4d6b3..6e60c0ed95 100644 --- a/ui/src/app/locale/locale.constant-ja_JA.json +++ b/ui/src/app/locale/locale.constant-ja_JA.json @@ -1,1528 +1,1530 @@ -{ - "access": { - "unauthorized": "無許可", - "unauthorized-access": "不正アクセス", - "unauthorized-access-text": "このリソースにアクセスするにはサインインする必要があります。", - "access-forbidden": "アクセス禁止", - "access-forbidden-text": "あなたはこの場所へのアクセス権を持っていません!この場所にアクセスしたい場合は、別のユーザーとサインインしてみてください。", - "refresh-token-expired": "セッションが終了しました", - "refresh-token-failed": "セッションをリフレッシュできません" - }, - "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": "ドラッグ", - "refresh": "リフレッシュ", - "undo": "元に戻す", - "copy": "コピー", - "paste": "ペースト", - "copy-reference": "コピーリファレンス", - "paste-reference": "参照貼り付け", - "import": "インポート", - "export": "輸出する", - "share-via": "{{provider}}" - }, - "aggregation": { - "aggregation": "集約", - "function": "データ集約機能", - "limit": "最大値", - "group-interval": "グループ化の間隔", - "min": "分", - "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": "ベースURLは必須です。", - "mail-from": "メール", - "mail-from-required": "メールの送信元が必要です。", - "smtp-protocol": "SMTPプロトコル", - "smtp-host": "SMTPホスト", - "smtp-host-required": "SMTPホストが必要です。", - "smtp-port": "SMTPポート", - "smtp-port-required": "smtpポートを指定する必要があります。", - "smtp-port-invalid": "それは有効なsmtpポートのようには見えません。", - "timeout-msec": "タイムアウト(ミリ秒)", - "timeout-required": "タイムアウトが必要です。", - "timeout-invalid": "それは有効なタイムアウトのようには見えません。", - "enable-tls": "TLSを有効にする", - "send-test-mail": "テストメールを送信する" - }, - "alarm": { - "alarm": "警報", - "alarms": "アラーム", - "select-alarm": "アラームを選択", - "no-alarms-matching": "'{{entity}}'発見されました。", - "alarm-required": "アラームが必要です", - "alarm-status": "アラーム状態", - "search-status": { - "ANY": "どれか", - "ACTIVE": "アクティブ", - "CLEARED": "クリアされた", - "ACK": "承認された", - "UNACK": "未確認の" - }, - "display-status": { - "ACTIVE_UNACK": "アクティブ未確認", - "ACTIVE_ACK": "Active Acknowledged", - "CLEARED_UNACK": "クリアされた未確認のメッセージ", - "CLEARED_ACK": "承認された承認済み" - }, - "no-alarms-prompt": "アラームが見つかりません", - "created-time": "作成時刻", - "type": "タイプ", - "severity": "重大度", - "originator": "創始者", - "originator-type": "発信者タイプ", - "details": "詳細", - "status": "状態", - "alarm-details": "アラームの詳細", - "start-time": "始まる時間", - "end-time": "終了時間", - "ack-time": "確認された時間", - "clear-time": "クリアされた時間", - "severity-critical": "クリティカル", - "severity-major": "メジャー", - "severity-minor": "マイナー", - "severity-warning": "警告", - "severity-indeterminate": "不確定", - "acknowledge": "認める", - "clear": "クリア", - "search": "アラームの検索", - "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} }選択された", - "no-data": "表示するデータがありません", - "polling-interval": "アラームポーリング間隔(秒)", - "polling-interval-required": "アラームのポーリング間隔が必要です。", - "min-polling-interval-message": "少なくとも1秒間のポーリング間隔が許可されます。", - "aknowledge-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", - "aknowledge-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?", - "clear-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", - "clear-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?" - }, - "alias": { - "add": "エイリアスを追加する", - "edit": "エイリアスを編集する", - "name": "エイリアス名", - "name-required": "エイリアス名は必須です", - "duplicate-alias": "同じ名前のエイリアスは既に存在します。", - "filter-type-single-entity": "単一のエンティティ", - "filter-type-entity-list": "エンティティリスト", - "filter-type-entity-name": "エンティティ名", - "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}}'", - "filter-type-device-type": "デバイスタイプ", - "filter-type-device-type-description": "'{{deviceType}}'", - "filter-type-device-type-and-name-description": "'{{deviceType}}''{{prefix}}'", - "filter-type-relations-query": "関係クエリ", - "filter-type-relations-query-description": "{{entities}}{{relationType}}{{direction}}{{rootEntity}}", - "filter-type-asset-search-query": "資産検索クエリ", - "filter-type-asset-search-query-description": "{{assetTypes}}{{relationType}}{{direction}}{{rootEntity}}", - "filter-type-device-search-query": "デバイス検索クエリ", - "filter-type-device-search-query-description": "{{deviceTypes}}{{relationType}}{{direction}}{{rootEntity}}", - "entity-filter": "エンティティフィルタ", - "resolve-multiple": "複数のエンティティとして解決する", - "filter-type": "フィルタタイプ", - "filter-type-required": "フィルタタイプが必要です。", - "entity-filter-no-entity-matched": "指定されたフィルタに一致するエンティティは見つかりませんでした。", - "no-entity-filter-specified": "エンティティフィルタが指定されていない", - "root-state-entity": "ルートとしてダッシュボードの状態エンティティを使用する", - "root-entity": "ルートエンティティ", - "state-entity-parameter-name": "状態エンティティのパラメータ名", - "default-state-entity": "デフォルト状態エンティティ", - "default-entity-parameter-name": "デフォルトでは", - "max-relation-level": "最大関連レベル", - "unlimited-level": "無制限レベル", - "state-entity": "ダッシュボードの状態エンティティ", - "all-entities": "すべてのエンティティ", - "any-relation": "どれか" - }, - "asset": { - "asset": "資産", - "assets": "資産", - "management": "資産運用管理", - "view-assets": "アセットの表示", - "add": "アセットを追加", - "assign-to-customer": "顧客に割り当てる", - "assign-asset-to-customer": "顧客に資産を割り当てる", - "assign-asset-to-customer-text": "顧客に割り当てる資産を選択してください", - "no-assets-text": "アセットが見つかりません", - "assign-to-customer-text": "資産を割り当てる顧客を選択してください", - "public": "パブリック", - "assignedToCustomer": "顧客に割り当てられた", - "make-public": "アセットを公開する", - "make-private": "アセットをプライベートにする", - "unassign-from-customer": "顧客からの割り当て解除", - "delete": "アセットを削除", - "asset-public": "資産は公開されています", - "asset-type": "資産の種類", - "asset-type-required": "資産の種類が必要です。", - "select-asset-type": "アセットタイプを選択", - "enter-asset-type": "アセットタイプを入力", - "any-asset": "すべてのアセット", - "no-asset-types-matching": "'{{entitySubtype}}'発見されました。", - "asset-type-list-empty": "選択されたアセットタイプはありません。", - "asset-types": "資産タイプ", - "name": "名", - "name-required": "名前は必須です。", - "description": "説明", - "type": "タイプ", - "type-required": "タイプが必要です。", - "details": "詳細", - "events": "イベント", - "add-asset-text": "新しいアセットを追加する", - "asset-details": "資産の詳細", - "assign-assets": "アセットの割り当て", - "assign-assets-text": "{ count, plural, 1 {1 asset} other {# assets} }顧客に", - "delete-assets": "アセットを削除する", - "unassign-assets": "アセットの割り当てを解除する", - "unassign-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }顧客から", - "assign-new-asset": "新しいアセットを割り当てる", - "delete-asset-title": "'{{assetName}}'?", - "delete-asset-text": "確認後、資産と関連するすべてのデータが回復不能になることに注意してください。", - "delete-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", - "delete-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }", - "delete-assets-text": "確認後、選択したすべての資産が削除され、関連するすべてのデータは回復不能になりますので注意してください。", - "make-public-asset-title": "'{{assetName}}'パブリック?", - "make-public-asset-text": "確認後、資産とそのすべてのデータは公開され、他の人がアクセスできるようになります。", - "make-private-asset-title": "'{{assetName}}'プライベート?", - "make-private-asset-text": "確認後、資産とそのすべてのデータは非公開にされ、他の人がアクセスすることはできません。", - "unassign-asset-title": "'{{assetName}}'?", - "unassign-asset-text": "確認後、資産は割り当て解除され、顧客はアクセスできなくなります。", - "unassign-asset": "アセットの割り当てを解除する", - "unassign-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", - "unassign-assets-text": "確認後、選択されたすべての資産が割り当て解除され、顧客がアクセスできなくなります。", - "copyId": "アセットIDをコピーする", - "idCopiedMessage": "アセットIDがクリップボードにコピーされました", - "select-asset": "アセットを選択", - "no-assets-matching": "'{{entity}}'発見されました。", - "asset-required": "資産が必要です", - "name-starts-with": "アセット名はで始まります", - "label": "ラベル" - }, - "attribute": { - "attributes": "属性", - "latest-telemetry": "最新テレメトリ", - "attributes-scope": "エンティティ属性のスコープ", - "scope-latest-telemetry": "最新テレメトリ", - "scope-client": "クライアントの属性", - "scope-server": "サーバーの属性", - "scope-shared": "共有属性", - "add": "属性を追加する", - "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-mode": "ウィジェットモード", - "next-widget": "次のウィジェット", - "prev-widget": "前のウィジェット", - "add-to-dashboard": "ダッシュボードに追加", - "add-widget-to-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-relation-add-or-update": "関係が更新されました", - "type-relation-delete": "関係が削除されました", - "type-relations-delete": "すべてのリレーションを削除", - "type-alarm-ack": "承認された", - "type-alarm-clear": "クリアされた", - "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": "住所", - "address2": "アドレス2", - "phone": "電話", - "email": "Eメール", - "no-address": "住所がありません" - }, - "common": { - "username": "ユーザー名", - "password": "パスワード", - "enter-username": "ユーザーネームを入力してください", - "enter-password": "パスワードを入力する", - "enter-search": "検索を入力" - }, - "content-type": { - "json": "Json", - "text": "テキスト", - "binary": "バイナリ(Base64)" - }, - "customer": { - "customer": "顧客", - "customers": "顧客", - "management": "顧客管理", - "dashboard": "カスタマーダッシュボード", - "dashboards": "カスタマーダッシュボード", - "devices": "顧客デバイス", - "assets": "顧客資産", - "public-dashboards": "パブリックダッシュボード", - "public-devices": "パブリックデバイス", - "public-assets": "公的資産", - "add": "顧客を追加", - "delete": "顧客を削除する", - "manage-customer-users": "顧客ユーザーを管理する", - "manage-customer-devices": "顧客のデバイスを管理する", - "manage-customer-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-users": "ユーザーを管理する", - "manage-assets": "アセットを管理する", - "manage-devices": "デバイスを管理する", - "manage-dashboards": "ダッシュボードの管理", - "title": "タイトル", - "title-required": "タイトルは必須です。", - "description": "説明", - "details": "詳細", - "events": "イベント", - "copyId": "顧客IDをコピー", - "idCopiedMessage": "顧客IDがクリップボードにコピーされました", - "select-customer": "顧客を選択", - "no-customers-matching": "'{{entity}}'発見されました。", - "customer-required": "顧客は必須です", - "select-default-customer": "デフォルトの顧客を選択", - "default-customer": "デフォルトの顧客", - "default-customer-required": "テナントレベルのダッシュボードをデバッグするには、デフォルトの顧客が必要です" - }, - "datetime": { - "date-from": "デートから", - "time-from": "からの時間", - "date-to": "日付", - "time-to": "の時間" - }, - "dashboard": { - "dashboard": "ダッシュボード", - "dashboards": "ダッシュボード", - "management": "ダッシュボード管理", - "view-dashboards": "ダッシュボードを表示する", - "add": "ダッシュボードを追加", - "assign-dashboard-to-customer": "顧客にダッシュボードを割り当てる", - "assign-dashboard-to-customer-text": "顧客に割り当てるダッシュボードを選択してください", - "assign-to-customer-text": "ダッシュボードを割り当てる顧客を選択してください", - "assign-to-customer": "顧客に割り当てる", - "unassign-from-customer": "顧客からの割り当て解除", - "make-public": "ダッシュボードを公開する", - "make-private": "ダッシュボードを非公開にする", - "manage-assigned-customers": "割り当てられた顧客を管理する", - "assigned-customers": "割り当てられた顧客", - "assign-to-customers": "顧客にダッシュボードを割り当てる", - "assign-to-customers-text": "ダッシュボードを割り当てる顧客を選択してください", - "unassign-from-customers": "顧客からのダッシュボードの割り当て解除", - "unassign-from-customers-text": "ダッシュボードから割り当て解除する顧客を選択してください", - "no-dashboards-text": "ダッシュボードが見つかりません", - "no-widgets": "ウィジェットは設定されていません", - "add-widget": "新しいウィジェットを追加", - "title": "タイトル", - "select-widget-title": "ウィジェットを選択", - "select-widget-subtitle": "利用可能なウィジェットタイプのリスト", - "delete": "ダッシュボードの削除", - "title-required": "タイトルは必須です。", - "description": "説明", - "details": "詳細", - "dashboard-details": "ダッシュボードの詳細", - "add-dashboard-text": "新しいダッシュボードを追加する", - "assign-dashboards": "ダッシュボードの割り当て", - "assign-new-dashboard": "新しいダッシュボードを割り当てる", - "assign-dashboards-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客に", - "unassign-dashboards-action-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", - "delete-dashboards": "ダッシュボードの削除", - "unassign-dashboards": "ダッシュボードの割り当てを解除する", - "unassign-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", - "delete-dashboard-title": "'{{dashboardTitle}}'?", - "delete-dashboard-text": "確認後、ダッシュボードとすべての関連データが回復不能になるので注意してください。", - "delete-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", - "delete-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }", - "delete-dashboards-text": "注意してください。確認後、選択したダッシュボードはすべて削除され、関連するすべてのデータは回復不能になります。", - "unassign-dashboard-title": "'{{dashboardTitle}}'?", - "unassign-dashboard-text": "確認後、ダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", - "unassign-dashboard": "ダッシュボードの割り当てを解除する", - "unassign-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", - "unassign-dashboards-text": "確認の後、選択したすべてのダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", - "public-dashboard-title": "ダッシュボードは公開されました", - "public-dashboard-text": "{{dashboardTitle}} is now public and accessible via next public link:", - "public-dashboard-notice": "注:データにアクセスするために、関連するデバイスを公開することを忘れないでください。", - "make-private-dashboard-title": "'{{dashboardTitle}}'プライベート?", - "make-private-dashboard-text": "確認の後、ダッシュボードはプライベートにされ、他の人がアクセスすることはできません。", - "make-private-dashboard": "ダッシュボードを非公開にする", - "socialshare-text": "'{{dashboardTitle}}'ThingsBoardを搭載", - "socialshare-title": "'{{dashboardTitle}}'ThingsBoardを搭載", - "select-dashboard": "ダッシュボードを選択", - "no-dashboards-matching": "'{{entity}}'発見されました。", - "dashboard-required": "ダッシュボードが必要です。", - "select-existing": "既存のダッシュボードを選択", - "create-new": "新しいダッシュボードを作成する", - "new-dashboard-title": "新しいダッシュボードのタイトル", - "open-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": "ウィジェット間のマージン", - "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-row-height": "モバイル行の高さ、px", - "mobile-row-height-required": "モバイル行の高さ値が必要です。", - "min-mobile-row-height-message": "最小の行の高さの値として、5ピクセルしか許可されません。", - "max-mobile-row-height-message": "移動可能な行の高さの最大値として許可されるのは200ピクセルだけです。", - "display-title": "ダッシュボードのタイトルを表示する", - "toolbar-always-open": "ツールバーを開いたままにする", - "title-color": "タイトルカラー", - "display-dashboards-selection": "ダッシュボードの選択を表示する", - "display-entities-selection": "エンティティの選択を表示する", - "display-dashboard-timewindow": "タイムウィンドウを表示する", - "display-dashboard-export": "エクスポートの表示", - "import": "インポートダッシュボード", - "export": "エクスポートダッシュボード", - "export-failed-error": "{{error}}", - "create-new-dashboard": "新しいダッシュボードを作成する", - "dashboard-file": "ダッシュボードファイル", - "invalid-dashboard-file-error": "ダッシュボードをインポートできません:ダッシュボードのデータ構造が無効です。", - "dashboard-import-missing-aliases-title": "インポートされたダッシュボードで使用されるエイリアスを設定する", - "create-new-widget": "新しいウィジェットを作成する", - "import-widget": "インポートウィジェット", - "widget-file": "ウィジェットファイル", - "invalid-widget-file-error": "ウィジェットをインポートできません:ウィジェットのデータ構造が無効です。", - "widget-import-missing-aliases-title": "インポートされたウィジェットで使用されるエイリアスを設定する", - "open-toolbar": "ダッシュボードツールバーを開く", - "close-toolbar": "ツールバーを閉じる", - "configuration-error": "設定エラー", - "alias-resolution-error-title": "ダッシュボードエイリアス設定エラー", - "invalid-aliases-config": "エイリアスフィルタの一部に一致するデバイスを見つけることができません。
この問題を解決するには、管理者に連絡してください。", - "select-devices": "デバイスの選択", - "assignedToCustomer": "顧客に割り当てられた", - "assignedToCustomers": "顧客に割り当てられた", - "public": "パブリック", - "public-link": "パブリックリンク", - "copy-public-link": "パブリックリンクをコピーする", - "public-link-copied-message": "ダッシュボードのパブリックリンクがクリップボードにコピーされました", - "manage-states": "ダッシュボードの状態を管理する", - "states": "ダッシュボードの状態", - "search-states": "検索ダッシュボードの状態", - "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} }選択された", - "edit-state": "ダッシュボードの状態を編集する", - "delete-state": "ダッシュボードの状態を削除する", - "add-state": "ダッシュボードの状態を追加する", - "state": "ダッシュボードの状態", - "state-name": "名", - "state-name-required": "ダッシュボードの状態名は必須です。", - "state-id": "状態ID", - "state-id-required": "ダッシュボードの状態IDは必須です。", - "state-id-exists": "同じIDを持つダッシュボードの状態は既に存在します。", - "is-root-state": "ルート状態", - "delete-state-title": "ダッシュボードの状態を削除する", - "delete-state-text": "'{{stateName}}'?", - "show-details": "詳細を表示", - "hide-details": "詳細を隠す", - "select-state": "ターゲット状態を選択する", - "state-controller": "状態コントローラ" - }, - "datakey": { - "settings": "設定", - "advanced": "上級", - "label": "ラベル", - "color": "色", - "units": "値の隣に表示する特別なシンボル", - "decimals": "浮動小数点の後の桁数", - "data-generation-func": "データ生成関数", - "use-data-post-processing-func": "データ後処理機能を使用する", - "configuration": "データキー設定", - "timeseries": "タイムズ", - "attributes": "属性", - "alarm": "アラームフィールド", - "timeseries-required": "エンティティの時系列データが必要です。", - "timeseries-or-attributes-required": "エンティティのtimeseries /属性は必須です。", - "maximum-timeseries-or-attributes": "{ count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", - "alarm-fields-required": "アラームフィールドが必要です。", - "function-types": "関数型", - "function-types-required": "関数型が必要です。", - "maximum-function-types": "{ count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }" - }, - "datasource": { - "type": "データソースタイプ", - "name": "名", - "add-datasource-prompt": "データソースを追加してください" - }, - "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}}'
デバイスエイリアスは、ダッシュボード内で一意である必要があります。", - "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": "アクセストークンの長さは、1〜20文字でなければなりません。", - "rsa-key": "RSA公開鍵", - "rsa-key-required": "RSA公開鍵が必要です。", - "secret": "秘密", - "secret-required": "秘密が必要です。", - "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": "説明", - "events": "イベント", - "details": "詳細", - "copyId": "デバイスIDをコピーする", - "copyAccessToken": "コピーアクセストークン", - "idCopiedMessage": "デバイスIDがクリップボードにコピーされました", - "accessTokenCopiedMessage": "デバイスアクセストークンがクリップボードにコピーされました", - "assignedToCustomer": "顧客に割り当てられた", - "unable-delete-device-alias-title": "デバイスエイリアスを削除できません", - "unable-delete-device-alias-text": "'{{deviceAlias}}'{{widgetsList}}", - "is-gateway": "ゲートウェイです", - "public": "パブリック", - "device-public": "デバイスは公開されています", - "select-device": "デバイスの選択" - }, - "dialog": { - "close": "ダイアログを閉じる" - }, - "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}}'{{widgetsList}}", - "duplicate-alias-error": "'{{alias}}'
エンティティのエイリアスは、ダッシュボード内で一意である必要があります。", - "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": "資産", - "type-assets": "資産", - "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", - "asset-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": "ダッシュボード", - "type-dashboards": "ダッシュボード", - "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", - "dashboard-name-starts-with": "'{{prefix}}'", - "type-alarm": "警報", - "type-alarms": "アラーム", - "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", - "alarm-name-starts-with": "'{{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-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": "表示するデータがありません" - }, - "event": { - "event-type": "イベントタイプ", - "type-error": "エラー", - "type-lc-event": "ライフサイクルイベント", - "type-stats": "統計", - "type-debug-rule-node": "デバッグ", - "type-debug-rule-chain": "デバッグ", - "no-events-prompt": "イベントは見つかりませんでした", - "error": "エラー", - "alarm": "警報", - "event-time": "イベント時間", - "server": "サーバ", - "body": "体", - "method": "方法", - "type": "タイプ", - "entity": "エンティティ", - "message-id": "メッセージID", - "message-type": "メッセージタイプ", - "data-type": "データ・タイプ", - "relation-type": "関係タイプ", - "metadata": "メタデータ", - "data": "データ", - "event": "イベント", - "status": "状態", - "success": "成功", - "failed": "失敗", - "messages-processed": "処理されたメッセージ", - "errors-occurred": "エラーが発生しました" - }, - "extension": { - "extensions": "拡張機能", - "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} }選択された", - "type": "タイプ", - "key": "キー", - "value": "値", - "id": "イド", - "extension-id": "内線番号", - "extension-type": "拡張タイプ", - "transformer-json": "JSON *", - "unique-id-required": "現在の拡張IDは既に存在します。", - "delete": "拡張子を削除", - "add": "内線番号を追加", - "edit": "拡張機能を編集する", - "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": "ブローカー", - "add-broker": "ブローカーを追加", - "host": "ホスト", - "port": "ポート", - "port-range": "ポートは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": "サーバー側RPC", - "add-server-side-rpc-request": "サーバー側RPC要求を追加する", - "device-name-filter": "デバイス名フィルタ", - "attribute-filter": "属性フィルタ", - "method-filter": "方法フィルター", - "request-topic-expression": "トピック表現を要求する", - "response-timeout": "応答タイムアウト(ミリ秒)", - "topic-expression": "トピック表現", - "client-scope": "クライアントスコープ", - "add-device": "デバイスを追加", - "opc-server": "サーバー", - "opc-add-server": "サーバーを追加", - "opc-add-server-prompt": "サーバーを追加してください", - "opc-application-name": "アプリケーション名", - "opc-application-uri": "アプリケーションURI", - "opc-scan-period-in-seconds": "スキャン時間(秒)", - "opc-security": "セキュリティ", - "opc-identity": "身元", - "opc-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": "サーバー/スレーブ", - "modbus-add-server": "サーバー/スレーブを追加する", - "modbus-add-server-prompt": "サーバー/スレーブを追加してください", - "modbus-transport": "輸送", - "modbus-port-name": "シリアルポート名", - "modbus-encoding": "エンコーディング", - "modbus-parity": "パリティ", - "modbus-baudrate": "ボーレート", - "modbus-databits": "データビット", - "modbus-stopbits": "ストップビット", - "modbus-databits-range": "データビットは7〜8の範囲内にある必要があります。", - "modbus-stopbits-range": "ストップビットは1〜2の範囲内でなければなりません。", - "modbus-unit-id": "ユニットID", - "modbus-unit-id-range": "ユニットIDは1〜247の範囲で指定してください。", - "modbus-device-name": "装置名", - "modbus-poll-period": "投票期間(ミリ秒)", - "modbus-attributes-poll-period": "属性のポーリング期間(ミリ秒)", - "modbus-timeseries-poll-period": "時系列ポーリング期間(ミリ秒)", - "modbus-poll-period-range": "投票期間は正の値でなければなりません。", - "modbus-tag": "タグ", - "modbus-function": "関数", - "modbus-register-address": "登録アドレス", - "modbus-register-address-range": "レジスタのアドレスは0〜65535の範囲内である必要があります。", - "modbus-register-bit-index": "ビットインデックス", - "modbus-register-bit-index-range": "ビットインデックスは0〜15の範囲内である必要があります。", - "modbus-register-count": "レジスタ数", - "modbus-register-count-range": "レジスタ数は正の値でなければなりません。", - "modbus-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": "アバター", - "open-user-menu": "ユーザーメニューを開く" - }, - "import": { - "no-file": "ファイルが選択されていません", - "drop-file": "JSONファイルをドロップするか、アップロードするファイルをクリックして選択します。" - }, - "item": { - "selected": "選択された" - }, - "js-func": { - "no-return-error": "関数は値を返す必要があります!", - "return-type-mismatch": "'{{type}}'タイプ!", - "tidy": "きちんとした" - }, - "key-val": { - "key": "キー", - "value": "値", - "remove-entry": "エントリを削除", - "add-entry": "エントリを追加", - "no-data": "エントリなし" - }, - "layout": { - "layout": "レイアウト", - "manage": "レイアウトの管理", - "settings": "レイアウト設定", - "color": "色", - "main": "メイン", - "right": "右", - "select": "ターゲットレイアウトを選択" - }, - "legend": { - "position": "伝説の位置", - "show-max": "最大値を表示", - "show-min": "最小値を表示する", - "show-avg": "平均値を表示", - "show-total": "合計値を表示", - "settings": "凡例の設定", - "min": "分", - "max": "最大", - "avg": "平均", - "total": "合計" - }, - "login": { - "login": "ログイン", - "request-password-reset": "リクエストパスワードのリセット", - "reset-password": "パスワードを再設定する", - "create-password": "パスワードの作成", - "passwords-mismatch-error": "入力されたパスワードは同じでなければなりません!", - "password-again": "パスワードをもう一度", - "sign-in": "サインインしてください", - "username": "ユーザー名(電子メール)", - "remember-me": "私を覚えてますか", - "forgot-password": "パスワードをお忘れですか?", - "password-reset": "パスワードのリセット", - "new-password": "新しいパスワード", - "new-password-again": "新しいパスワードを再入力", - "password-link-sent-message": "パスワードリセットリンクが正常に送信されました!", - "email": "Eメール" - }, - "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} other {# relations} }選択された", - "type": "タイプ", - "to-entity-type": "エンティティタイプへ", - "to-entity-name": "エンティティ名に", - "from-entity-type": "エンティティタイプから", - "from-entity-name": "エンティティ名から", - "to-entity": "実体へ", - "from-entity": "エンティティから", - "delete": "関係を削除する", - "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} other {# 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": "ルールチェーンIDのコピー", - "idCopiedMessage": "ルールチェーンIDがクリップボードにコピーされました", - "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": "選択を削除します", - "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-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": "助けて" - }, - "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": "テナントIDをコピーする", - "idCopiedMessage": "テナントIDがクリップボードにコピーされました", - "select-tenant": "テナントを選択", - "no-tenants-matching": "'{{entity}}'発見されました。", - "tenant-required": "テナントが必要です" - }, - "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": "ユーザー", - "customer-users": "顧客ユーザー", - "tenant-admins": "テナント管理者", - "sys-admin": "システム管理者", - "tenant-admin": "テナント管理者", - "customer": "顧客", - "anonymous": "匿名", - "add": "ユーザーを追加する", - "delete": "ユーザーを削除", - "add-user-text": "新しいユーザーを追加", - "no-users-text": "ユーザが見つかりませんでした", - "user-details": "ユーザーの詳細", - "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": "アクティベーション電子メールが正常に送信されました!", - "resend-activation": "アクティブ化を再送", - "email": "Eメール", - "email-required": "電子メールが必要です。", - "invalid-email-format": "メールフォーマットが無効です。", - "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": "アクティベーションメールを送信する", - "activation-link": "ユーザーアクティベーションリンク", - "activation-link-text": "activation link :", - "copy-activation-link": "アクティブ化リンクをコピーする", - "activation-link-copied-message": "ユーザーのアクティベーションリンクがクリップボードにコピーされました", - "details": "詳細" - }, - "value": { - "type": "値のタイプ", - "string": "文字列", - "string-value": "文字列値", - "integer": "整数", - "integer-value": "整数値", - "invalid-integer-value": "整数値が無効です", - "double": "ダブル", - "double-value": "二重価値", - "boolean": "ブール", - "boolean-value": "ブール値", - "false": "偽", - "true": "真", - "long": "長いです" - }, - "widget": { - "widget-library": "ウィジェットライブラリ", - "widget-bundle": "ウィジェットバンドル", - "select-widgets-bundle": "ウィジェットのバンドルを選択", - "management": "ウィジェット管理", - "editor": "ウィジェットエディタ", - "widget-type-not-found": "ウィジェットの設定を読み込む際に問題が発生しました。
おそらく関連付けられているウィジェットのタイプが削除されています。", - "widget-type-load-error": "次のエラーのためにウィジェットが読み込まれませんでした:", - "remove": "ウィジェットを削除", - "edit": "ウィジェットの編集", - "remove-widget-title": "'{{widgetTitle}}'?", - "remove-widget-text": "確認後、ウィジェットと関連するすべてのデータは回復不能になります。", - "timeseries": "時系列", - "search-data": "検索データ", - "no-data-found": "何もデータが見つかりませんでした", - "latest-values": "最新の値", - "rpc": "コントロールウィジェット", - "alarm": "アラームウィジェット", - "static": "静的ウィジェット", - "select-widget-type": "ウィジェットタイプを選択", - "missing-widget-title-error": "ウィジェットのタイトルを指定する必要があります!", - "widget-saved": "ウィジェットが保存されました", - "unable-to-save-widget-error": "ウィジェットを保存できません!ウィジェットにエラーがあります!", - "save": "ウィジェットを保存", - "saveAs": "ウィジェットを次のように保存する", - "save-widget-type-as": "ウィジェットタイプを次のように保存します", - "save-widget-type-as-text": "新しいウィジェットのタイトルを入力したり、ターゲットウィジェットのバンドルを選択してください", - "toggle-fullscreen": "フルスクリーン切り替え", - "run": "ウィジェットを実行する", - "title": "ウィジェットのタイトル", - "title-required": "ウィジェットのタイトルが必要です。", - "type": "ウィジェットタイプ", - "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": "'{{widgetName}}'?", - "remove-widget-type-text": "確認後、ウィジェットのタイプと関連するすべてのデータは回復不能になります。", - "remove-widget-type": "ウィジェットタイプを削除", - "add-widget-type": "新しいウィジェットタイプを追加する", - "widget-type-load-failed-error": "ウィジェットタイプの読み込みに失敗しました!", - "widget-template-load-failed-error": "ウィジェットテンプレートを読み込めませんでした!", - "add": "ウィジェットを追加", - "undo": "ウィジェットの変更を元に戻す", - "export": "ウィジェットの書き出し" - }, - "widget-action": { - "header-button": "ウィジェットのヘッダーボタン", - "open-dashboard-state": "新しいダッシュボードの状態に移動する", - "update-dashboard-state": "現在のダッシュボードの状態を更新する", - "open-dashboard": "他のダッシュボードに移動する", - "custom": "カスタムアクション", - "target-dashboard-state": "ターゲットダッシュボードの状態", - "target-dashboard-state-required": "ターゲットダッシュボードの状態が必要です", - "set-entity-from-widget": "エンティティをウィジェットから設定する", - "target-dashboard": "ターゲットダッシュボード", - "open-right-layout": "右ダッシュボードレイアウトを開く(モバイルビュー)" - }, - "widgets-bundle": { - "current": "現在のバンドル", - "widgets-bundles": "ウィジェットバンドル", - "add": "ウィジェットのバンドルを追加", - "delete": "ウィジェットのバンドルを削除する", - "title": "タイトル", - "title-required": "タイトルは必須です。", - "add-widgets-bundle-text": "新しいウィジェットのバンドルを追加する", - "no-widgets-bundles-text": "ウィジェットバンドルが見つかりません", - "empty": "ウィジェットのバンドルが空です", - "details": "詳細", - "widgets-bundle-details": "ウィジェットのバンドルの詳細", - "delete-widgets-bundle-title": "'{{widgetsBundleTitle}}'?", - "delete-widgets-bundle-text": "確認後、ウィジェットはバンドルされ、関連するすべてのデータは回復不能になります。", - "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": "確認後、選択したすべてのウィジェットバンドルは削除され、関連するすべてのデータは回復不能になります。", - "no-widgets-bundles-matching": "'{{widgetsBundle}}'発見されました。", - "widgets-bundle-required": "ウィジェットバンドルが必要です。", - "system": "システム", - "import": "インポートウィジェットバンドル", - "export": "ウィジェットのエクスポートバンドル", - "export-failed-error": "{{error}}", - "create-new-widgets-bundle": "新しいウィジェットバンドルを作成する", - "widgets-bundle-file": "ウィジェットのバンドルファイル", - "invalid-widgets-bundle-file-error": "ウィジェットをインポートできません。bundle:データ構造が無効です。" - }, - "widget-config": { - "data": "データ", - "settings": "設定", - "advanced": "上級", - "title": "タイトル", - "general-settings": "一般設定", - "display-title": "タイトルを表示", - "drop-shadow": "影を落とす", - "enable-fullscreen": "フルスクリーンを有効にする", - "background-color": "背景色", - "text-color": "テキストの色", - "padding": "パディング", - "margin": "マージン", - "widget-style": "ウィジェットスタイル", - "title-style": "タイトルスタイル", - "mobile-mode-settings": "モバイルモードの設定", - "order": "注文", - "height": "高さ", - "units": "値の隣に表示する特別なシンボル", - "decimals": "浮動小数点の後の桁数", - "timewindow": "タイムウィンドウ", - "use-dashboard-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": "ターゲットデバイス", - "alarm-source": "アラームソース", - "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": "ウィジェットアクションを削除する", - "delete-action-text": "'{{actionName}}'?" - }, - "widget-type": { - "import": "インポートウィジェットタイプ", - "export": "ウィジェットのタイプをエクスポートする", - "export-failed-error": "{{error}}", - "create-new-widget-type": "新しいウィジェットタイプを作成する", - "widget-type-file": "ウィジェットタイプファイル", - "invalid-widget-type-file-error": "ウィジェットタイプをインポートできません:ウィジェットタイプのデータ構造が無効です。" - }, - "widgets": { - "date-range-navigator": { - "localizationMap": { - "Sun": "日", - "Mon": "月", - "Tue": "火", - "Wed": "水", - "Thu": "木", - "Fri": "金", - "Sat": "土", - "Jan": "1月", - "Feb": "2月", - "Mar": "3月", - "Apr": "4月", - "May": "5月", - "Jun": "6月", - "Jul": "7月", - "Aug": "8月", - "Sep": "9月", - "Oct": "10月", - "Nov": "11月", - "Dec": "12月", - "January": "1月", - "February": "2月", - "March": "行進", - "April": "4月", - "June": "六月", - "July": "7月", - "August": "8月", - "September": "9月", - "October": "10月", - "November": "11月", - "December": "12月", - "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": "すべてのアイコンを表示する" - }, - "custom": { - "widget-action": { - "action-cell-button": "アクションセルボタン", - "row-click": "行のクリック", - "polygon-click": "ポリゴンクリック", - "marker-click": "マーカークリック", - "tooltip-tag-action": "ツールチップのタグアクション" - } - }, - "language": { - "language": "言語", - "locales": { - "de_DE": "ドイツ語", - "fr_FR": "フランス語", - "en_US": "英語", - "ko_KR": "韓国語", - "it_IT": "イタリアの", - "zh_CN": "中国語", - "ru_RU": "ロシア", - "es_ES": "スペイン語", - "ja_JA": "日本語", - "tr_TR": "トルコ語", - "fa_IR": "ペルシャ語", - "uk_UA": "ウクライナ語", - "cs_CZ": "チェコ語で" - } - } +{ + "access": { + "unauthorized": "無許可", + "unauthorized-access": "不正アクセス", + "unauthorized-access-text": "このリソースにアクセスするにはサインインする必要があります。", + "access-forbidden": "アクセス禁止", + "access-forbidden-text": "あなたはこの場所へのアクセス権を持っていません!この場所にアクセスしたい場合は、別のユーザーとサインインしてみてください。", + "refresh-token-expired": "セッションが終了しました", + "refresh-token-failed": "セッションをリフレッシュできません" + }, + "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": "ドラッグ", + "refresh": "リフレッシュ", + "undo": "元に戻す", + "copy": "コピー", + "paste": "ペースト", + "copy-reference": "コピーリファレンス", + "paste-reference": "参照貼り付け", + "import": "インポート", + "export": "輸出する", + "share-via": "{{provider}}" + }, + "aggregation": { + "aggregation": "集約", + "function": "データ集約機能", + "limit": "最大値", + "group-interval": "グループ化の間隔", + "min": "分", + "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": "ベースURLは必須です。", + "mail-from": "メール", + "mail-from-required": "メールの送信元が必要です。", + "smtp-protocol": "SMTPプロトコル", + "smtp-host": "SMTPホスト", + "smtp-host-required": "SMTPホストが必要です。", + "smtp-port": "SMTPポート", + "smtp-port-required": "smtpポートを指定する必要があります。", + "smtp-port-invalid": "それは有効なsmtpポートのようには見えません。", + "timeout-msec": "タイムアウト(ミリ秒)", + "timeout-required": "タイムアウトが必要です。", + "timeout-invalid": "それは有効なタイムアウトのようには見えません。", + "enable-tls": "TLSを有効にする", + "tls-version": "TLSバージョン", + "enter-tls-version" : "TLSバージョンを入力してください", + "send-test-mail": "テストメールを送信する" + }, + "alarm": { + "alarm": "警報", + "alarms": "アラーム", + "select-alarm": "アラームを選択", + "no-alarms-matching": "'{{entity}}'発見されました。", + "alarm-required": "アラームが必要です", + "alarm-status": "アラーム状態", + "search-status": { + "ANY": "どれか", + "ACTIVE": "アクティブ", + "CLEARED": "クリアされた", + "ACK": "承認された", + "UNACK": "未確認の" + }, + "display-status": { + "ACTIVE_UNACK": "アクティブ未確認", + "ACTIVE_ACK": "Active Acknowledged", + "CLEARED_UNACK": "クリアされた未確認のメッセージ", + "CLEARED_ACK": "承認された承認済み" + }, + "no-alarms-prompt": "アラームが見つかりません", + "created-time": "作成時刻", + "type": "タイプ", + "severity": "重大度", + "originator": "創始者", + "originator-type": "発信者タイプ", + "details": "詳細", + "status": "状態", + "alarm-details": "アラームの詳細", + "start-time": "始まる時間", + "end-time": "終了時間", + "ack-time": "確認された時間", + "clear-time": "クリアされた時間", + "severity-critical": "クリティカル", + "severity-major": "メジャー", + "severity-minor": "マイナー", + "severity-warning": "警告", + "severity-indeterminate": "不確定", + "acknowledge": "認める", + "clear": "クリア", + "search": "アラームの検索", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} }選択された", + "no-data": "表示するデータがありません", + "polling-interval": "アラームポーリング間隔(秒)", + "polling-interval-required": "アラームのポーリング間隔が必要です。", + "min-polling-interval-message": "少なくとも1秒間のポーリング間隔が許可されます。", + "aknowledge-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarms-title": "{ count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "{ count, plural, 1 {1 alarm} other {# alarms} }?" + }, + "alias": { + "add": "エイリアスを追加する", + "edit": "エイリアスを編集する", + "name": "エイリアス名", + "name-required": "エイリアス名は必須です", + "duplicate-alias": "同じ名前のエイリアスは既に存在します。", + "filter-type-single-entity": "単一のエンティティ", + "filter-type-entity-list": "エンティティリスト", + "filter-type-entity-name": "エンティティ名", + "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}}'", + "filter-type-device-type": "デバイスタイプ", + "filter-type-device-type-description": "'{{deviceType}}'", + "filter-type-device-type-and-name-description": "'{{deviceType}}''{{prefix}}'", + "filter-type-relations-query": "関係クエリ", + "filter-type-relations-query-description": "{{entities}}{{relationType}}{{direction}}{{rootEntity}}", + "filter-type-asset-search-query": "資産検索クエリ", + "filter-type-asset-search-query-description": "{{assetTypes}}{{relationType}}{{direction}}{{rootEntity}}", + "filter-type-device-search-query": "デバイス検索クエリ", + "filter-type-device-search-query-description": "{{deviceTypes}}{{relationType}}{{direction}}{{rootEntity}}", + "entity-filter": "エンティティフィルタ", + "resolve-multiple": "複数のエンティティとして解決する", + "filter-type": "フィルタタイプ", + "filter-type-required": "フィルタタイプが必要です。", + "entity-filter-no-entity-matched": "指定されたフィルタに一致するエンティティは見つかりませんでした。", + "no-entity-filter-specified": "エンティティフィルタが指定されていない", + "root-state-entity": "ルートとしてダッシュボードの状態エンティティを使用する", + "root-entity": "ルートエンティティ", + "state-entity-parameter-name": "状態エンティティのパラメータ名", + "default-state-entity": "デフォルト状態エンティティ", + "default-entity-parameter-name": "デフォルトでは", + "max-relation-level": "最大関連レベル", + "unlimited-level": "無制限レベル", + "state-entity": "ダッシュボードの状態エンティティ", + "all-entities": "すべてのエンティティ", + "any-relation": "どれか" + }, + "asset": { + "asset": "資産", + "assets": "資産", + "management": "資産運用管理", + "view-assets": "アセットの表示", + "add": "アセットを追加", + "assign-to-customer": "顧客に割り当てる", + "assign-asset-to-customer": "顧客に資産を割り当てる", + "assign-asset-to-customer-text": "顧客に割り当てる資産を選択してください", + "no-assets-text": "アセットが見つかりません", + "assign-to-customer-text": "資産を割り当てる顧客を選択してください", + "public": "パブリック", + "assignedToCustomer": "顧客に割り当てられた", + "make-public": "アセットを公開する", + "make-private": "アセットをプライベートにする", + "unassign-from-customer": "顧客からの割り当て解除", + "delete": "アセットを削除", + "asset-public": "資産は公開されています", + "asset-type": "資産の種類", + "asset-type-required": "資産の種類が必要です。", + "select-asset-type": "アセットタイプを選択", + "enter-asset-type": "アセットタイプを入力", + "any-asset": "すべてのアセット", + "no-asset-types-matching": "'{{entitySubtype}}'発見されました。", + "asset-type-list-empty": "選択されたアセットタイプはありません。", + "asset-types": "資産タイプ", + "name": "名", + "name-required": "名前は必須です。", + "description": "説明", + "type": "タイプ", + "type-required": "タイプが必要です。", + "details": "詳細", + "events": "イベント", + "add-asset-text": "新しいアセットを追加する", + "asset-details": "資産の詳細", + "assign-assets": "アセットの割り当て", + "assign-assets-text": "{ count, plural, 1 {1 asset} other {# assets} }顧客に", + "delete-assets": "アセットを削除する", + "unassign-assets": "アセットの割り当てを解除する", + "unassign-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }顧客から", + "assign-new-asset": "新しいアセットを割り当てる", + "delete-asset-title": "'{{assetName}}'?", + "delete-asset-text": "確認後、資産と関連するすべてのデータが回復不能になることに注意してください。", + "delete-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "{ count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "確認後、選択したすべての資産が削除され、関連するすべてのデータは回復不能になりますので注意してください。", + "make-public-asset-title": "'{{assetName}}'パブリック?", + "make-public-asset-text": "確認後、資産とそのすべてのデータは公開され、他の人がアクセスできるようになります。", + "make-private-asset-title": "'{{assetName}}'プライベート?", + "make-private-asset-text": "確認後、資産とそのすべてのデータは非公開にされ、他の人がアクセスすることはできません。", + "unassign-asset-title": "'{{assetName}}'?", + "unassign-asset-text": "確認後、資産は割り当て解除され、顧客はアクセスできなくなります。", + "unassign-asset": "アセットの割り当てを解除する", + "unassign-assets-title": "{ count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "確認後、選択されたすべての資産が割り当て解除され、顧客がアクセスできなくなります。", + "copyId": "アセットIDをコピーする", + "idCopiedMessage": "アセットIDがクリップボードにコピーされました", + "select-asset": "アセットを選択", + "no-assets-matching": "'{{entity}}'発見されました。", + "asset-required": "資産が必要です", + "name-starts-with": "アセット名はで始まります", + "label": "ラベル" + }, + "attribute": { + "attributes": "属性", + "latest-telemetry": "最新テレメトリ", + "attributes-scope": "エンティティ属性のスコープ", + "scope-latest-telemetry": "最新テレメトリ", + "scope-client": "クライアントの属性", + "scope-server": "サーバーの属性", + "scope-shared": "共有属性", + "add": "属性を追加する", + "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-mode": "ウィジェットモード", + "next-widget": "次のウィジェット", + "prev-widget": "前のウィジェット", + "add-to-dashboard": "ダッシュボードに追加", + "add-widget-to-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-relation-add-or-update": "関係が更新されました", + "type-relation-delete": "関係が削除されました", + "type-relations-delete": "すべてのリレーションを削除", + "type-alarm-ack": "承認された", + "type-alarm-clear": "クリアされた", + "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": "住所", + "address2": "アドレス2", + "phone": "電話", + "email": "Eメール", + "no-address": "住所がありません" + }, + "common": { + "username": "ユーザー名", + "password": "パスワード", + "enter-username": "ユーザーネームを入力してください", + "enter-password": "パスワードを入力する", + "enter-search": "検索を入力" + }, + "content-type": { + "json": "Json", + "text": "テキスト", + "binary": "バイナリ(Base64)" + }, + "customer": { + "customer": "顧客", + "customers": "顧客", + "management": "顧客管理", + "dashboard": "カスタマーダッシュボード", + "dashboards": "カスタマーダッシュボード", + "devices": "顧客デバイス", + "assets": "顧客資産", + "public-dashboards": "パブリックダッシュボード", + "public-devices": "パブリックデバイス", + "public-assets": "公的資産", + "add": "顧客を追加", + "delete": "顧客を削除する", + "manage-customer-users": "顧客ユーザーを管理する", + "manage-customer-devices": "顧客のデバイスを管理する", + "manage-customer-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-users": "ユーザーを管理する", + "manage-assets": "アセットを管理する", + "manage-devices": "デバイスを管理する", + "manage-dashboards": "ダッシュボードの管理", + "title": "タイトル", + "title-required": "タイトルは必須です。", + "description": "説明", + "details": "詳細", + "events": "イベント", + "copyId": "顧客IDをコピー", + "idCopiedMessage": "顧客IDがクリップボードにコピーされました", + "select-customer": "顧客を選択", + "no-customers-matching": "'{{entity}}'発見されました。", + "customer-required": "顧客は必須です", + "select-default-customer": "デフォルトの顧客を選択", + "default-customer": "デフォルトの顧客", + "default-customer-required": "テナントレベルのダッシュボードをデバッグするには、デフォルトの顧客が必要です" + }, + "datetime": { + "date-from": "デートから", + "time-from": "からの時間", + "date-to": "日付", + "time-to": "の時間" + }, + "dashboard": { + "dashboard": "ダッシュボード", + "dashboards": "ダッシュボード", + "management": "ダッシュボード管理", + "view-dashboards": "ダッシュボードを表示する", + "add": "ダッシュボードを追加", + "assign-dashboard-to-customer": "顧客にダッシュボードを割り当てる", + "assign-dashboard-to-customer-text": "顧客に割り当てるダッシュボードを選択してください", + "assign-to-customer-text": "ダッシュボードを割り当てる顧客を選択してください", + "assign-to-customer": "顧客に割り当てる", + "unassign-from-customer": "顧客からの割り当て解除", + "make-public": "ダッシュボードを公開する", + "make-private": "ダッシュボードを非公開にする", + "manage-assigned-customers": "割り当てられた顧客を管理する", + "assigned-customers": "割り当てられた顧客", + "assign-to-customers": "顧客にダッシュボードを割り当てる", + "assign-to-customers-text": "ダッシュボードを割り当てる顧客を選択してください", + "unassign-from-customers": "顧客からのダッシュボードの割り当て解除", + "unassign-from-customers-text": "ダッシュボードから割り当て解除する顧客を選択してください", + "no-dashboards-text": "ダッシュボードが見つかりません", + "no-widgets": "ウィジェットは設定されていません", + "add-widget": "新しいウィジェットを追加", + "title": "タイトル", + "select-widget-title": "ウィジェットを選択", + "select-widget-subtitle": "利用可能なウィジェットタイプのリスト", + "delete": "ダッシュボードの削除", + "title-required": "タイトルは必須です。", + "description": "説明", + "details": "詳細", + "dashboard-details": "ダッシュボードの詳細", + "add-dashboard-text": "新しいダッシュボードを追加する", + "assign-dashboards": "ダッシュボードの割り当て", + "assign-new-dashboard": "新しいダッシュボードを割り当てる", + "assign-dashboards-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客に", + "unassign-dashboards-action-text": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", + "delete-dashboards": "ダッシュボードの削除", + "unassign-dashboards": "ダッシュボードの割り当てを解除する", + "unassign-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }顧客から", + "delete-dashboard-title": "'{{dashboardTitle}}'?", + "delete-dashboard-text": "確認後、ダッシュボードとすべての関連データが回復不能になるので注意してください。", + "delete-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "注意してください。確認後、選択したダッシュボードはすべて削除され、関連するすべてのデータは回復不能になります。", + "unassign-dashboard-title": "'{{dashboardTitle}}'?", + "unassign-dashboard-text": "確認後、ダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", + "unassign-dashboard": "ダッシュボードの割り当てを解除する", + "unassign-dashboards-title": "{ count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "確認の後、選択したすべてのダッシュボードは割り当てられなくなり、顧客はアクセスできなくなります。", + "public-dashboard-title": "ダッシュボードは公開されました", + "public-dashboard-text": "{{dashboardTitle}} is now public and accessible via next public link:", + "public-dashboard-notice": "注:データにアクセスするために、関連するデバイスを公開することを忘れないでください。", + "make-private-dashboard-title": "'{{dashboardTitle}}'プライベート?", + "make-private-dashboard-text": "確認の後、ダッシュボードはプライベートにされ、他の人がアクセスすることはできません。", + "make-private-dashboard": "ダッシュボードを非公開にする", + "socialshare-text": "'{{dashboardTitle}}'ThingsBoardを搭載", + "socialshare-title": "'{{dashboardTitle}}'ThingsBoardを搭載", + "select-dashboard": "ダッシュボードを選択", + "no-dashboards-matching": "'{{entity}}'発見されました。", + "dashboard-required": "ダッシュボードが必要です。", + "select-existing": "既存のダッシュボードを選択", + "create-new": "新しいダッシュボードを作成する", + "new-dashboard-title": "新しいダッシュボードのタイトル", + "open-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": "ウィジェット間のマージン", + "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-row-height": "モバイル行の高さ、px", + "mobile-row-height-required": "モバイル行の高さ値が必要です。", + "min-mobile-row-height-message": "最小の行の高さの値として、5ピクセルしか許可されません。", + "max-mobile-row-height-message": "移動可能な行の高さの最大値として許可されるのは200ピクセルだけです。", + "display-title": "ダッシュボードのタイトルを表示する", + "toolbar-always-open": "ツールバーを開いたままにする", + "title-color": "タイトルカラー", + "display-dashboards-selection": "ダッシュボードの選択を表示する", + "display-entities-selection": "エンティティの選択を表示する", + "display-dashboard-timewindow": "タイムウィンドウを表示する", + "display-dashboard-export": "エクスポートの表示", + "import": "インポートダッシュボード", + "export": "エクスポートダッシュボード", + "export-failed-error": "{{error}}", + "create-new-dashboard": "新しいダッシュボードを作成する", + "dashboard-file": "ダッシュボードファイル", + "invalid-dashboard-file-error": "ダッシュボードをインポートできません:ダッシュボードのデータ構造が無効です。", + "dashboard-import-missing-aliases-title": "インポートされたダッシュボードで使用されるエイリアスを設定する", + "create-new-widget": "新しいウィジェットを作成する", + "import-widget": "インポートウィジェット", + "widget-file": "ウィジェットファイル", + "invalid-widget-file-error": "ウィジェットをインポートできません:ウィジェットのデータ構造が無効です。", + "widget-import-missing-aliases-title": "インポートされたウィジェットで使用されるエイリアスを設定する", + "open-toolbar": "ダッシュボードツールバーを開く", + "close-toolbar": "ツールバーを閉じる", + "configuration-error": "設定エラー", + "alias-resolution-error-title": "ダッシュボードエイリアス設定エラー", + "invalid-aliases-config": "エイリアスフィルタの一部に一致するデバイスを見つけることができません。
この問題を解決するには、管理者に連絡してください。", + "select-devices": "デバイスの選択", + "assignedToCustomer": "顧客に割り当てられた", + "assignedToCustomers": "顧客に割り当てられた", + "public": "パブリック", + "public-link": "パブリックリンク", + "copy-public-link": "パブリックリンクをコピーする", + "public-link-copied-message": "ダッシュボードのパブリックリンクがクリップボードにコピーされました", + "manage-states": "ダッシュボードの状態を管理する", + "states": "ダッシュボードの状態", + "search-states": "検索ダッシュボードの状態", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} }選択された", + "edit-state": "ダッシュボードの状態を編集する", + "delete-state": "ダッシュボードの状態を削除する", + "add-state": "ダッシュボードの状態を追加する", + "state": "ダッシュボードの状態", + "state-name": "名", + "state-name-required": "ダッシュボードの状態名は必須です。", + "state-id": "状態ID", + "state-id-required": "ダッシュボードの状態IDは必須です。", + "state-id-exists": "同じIDを持つダッシュボードの状態は既に存在します。", + "is-root-state": "ルート状態", + "delete-state-title": "ダッシュボードの状態を削除する", + "delete-state-text": "'{{stateName}}'?", + "show-details": "詳細を表示", + "hide-details": "詳細を隠す", + "select-state": "ターゲット状態を選択する", + "state-controller": "状態コントローラ" + }, + "datakey": { + "settings": "設定", + "advanced": "上級", + "label": "ラベル", + "color": "色", + "units": "値の隣に表示する特別なシンボル", + "decimals": "浮動小数点の後の桁数", + "data-generation-func": "データ生成関数", + "use-data-post-processing-func": "データ後処理機能を使用する", + "configuration": "データキー設定", + "timeseries": "タイムズ", + "attributes": "属性", + "alarm": "アラームフィールド", + "timeseries-required": "エンティティの時系列データが必要です。", + "timeseries-or-attributes-required": "エンティティのtimeseries /属性は必須です。", + "maximum-timeseries-or-attributes": "{ count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "アラームフィールドが必要です。", + "function-types": "関数型", + "function-types-required": "関数型が必要です。", + "maximum-function-types": "{ count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }" + }, + "datasource": { + "type": "データソースタイプ", + "name": "名", + "add-datasource-prompt": "データソースを追加してください" + }, + "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}}'
デバイスエイリアスは、ダッシュボード内で一意である必要があります。", + "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": "アクセストークンの長さは、1〜20文字でなければなりません。", + "rsa-key": "RSA公開鍵", + "rsa-key-required": "RSA公開鍵が必要です。", + "secret": "秘密", + "secret-required": "秘密が必要です。", + "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": "説明", + "events": "イベント", + "details": "詳細", + "copyId": "デバイスIDをコピーする", + "copyAccessToken": "コピーアクセストークン", + "idCopiedMessage": "デバイスIDがクリップボードにコピーされました", + "accessTokenCopiedMessage": "デバイスアクセストークンがクリップボードにコピーされました", + "assignedToCustomer": "顧客に割り当てられた", + "unable-delete-device-alias-title": "デバイスエイリアスを削除できません", + "unable-delete-device-alias-text": "'{{deviceAlias}}'{{widgetsList}}", + "is-gateway": "ゲートウェイです", + "public": "パブリック", + "device-public": "デバイスは公開されています", + "select-device": "デバイスの選択" + }, + "dialog": { + "close": "ダイアログを閉じる" + }, + "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}}'{{widgetsList}}", + "duplicate-alias-error": "'{{alias}}'
エンティティのエイリアスは、ダッシュボード内で一意である必要があります。", + "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": "資産", + "type-assets": "資産", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-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": "ダッシュボード", + "type-dashboards": "ダッシュボード", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "'{{prefix}}'", + "type-alarm": "警報", + "type-alarms": "アラーム", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "'{{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-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": "表示するデータがありません" + }, + "event": { + "event-type": "イベントタイプ", + "type-error": "エラー", + "type-lc-event": "ライフサイクルイベント", + "type-stats": "統計", + "type-debug-rule-node": "デバッグ", + "type-debug-rule-chain": "デバッグ", + "no-events-prompt": "イベントは見つかりませんでした", + "error": "エラー", + "alarm": "警報", + "event-time": "イベント時間", + "server": "サーバ", + "body": "体", + "method": "方法", + "type": "タイプ", + "entity": "エンティティ", + "message-id": "メッセージID", + "message-type": "メッセージタイプ", + "data-type": "データ・タイプ", + "relation-type": "関係タイプ", + "metadata": "メタデータ", + "data": "データ", + "event": "イベント", + "status": "状態", + "success": "成功", + "failed": "失敗", + "messages-processed": "処理されたメッセージ", + "errors-occurred": "エラーが発生しました" + }, + "extension": { + "extensions": "拡張機能", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} }選択された", + "type": "タイプ", + "key": "キー", + "value": "値", + "id": "イド", + "extension-id": "内線番号", + "extension-type": "拡張タイプ", + "transformer-json": "JSON *", + "unique-id-required": "現在の拡張IDは既に存在します。", + "delete": "拡張子を削除", + "add": "内線番号を追加", + "edit": "拡張機能を編集する", + "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": "ブローカー", + "add-broker": "ブローカーを追加", + "host": "ホスト", + "port": "ポート", + "port-range": "ポートは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": "サーバー側RPC", + "add-server-side-rpc-request": "サーバー側RPC要求を追加する", + "device-name-filter": "デバイス名フィルタ", + "attribute-filter": "属性フィルタ", + "method-filter": "方法フィルター", + "request-topic-expression": "トピック表現を要求する", + "response-timeout": "応答タイムアウト(ミリ秒)", + "topic-expression": "トピック表現", + "client-scope": "クライアントスコープ", + "add-device": "デバイスを追加", + "opc-server": "サーバー", + "opc-add-server": "サーバーを追加", + "opc-add-server-prompt": "サーバーを追加してください", + "opc-application-name": "アプリケーション名", + "opc-application-uri": "アプリケーションURI", + "opc-scan-period-in-seconds": "スキャン時間(秒)", + "opc-security": "セキュリティ", + "opc-identity": "身元", + "opc-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": "サーバー/スレーブ", + "modbus-add-server": "サーバー/スレーブを追加する", + "modbus-add-server-prompt": "サーバー/スレーブを追加してください", + "modbus-transport": "輸送", + "modbus-port-name": "シリアルポート名", + "modbus-encoding": "エンコーディング", + "modbus-parity": "パリティ", + "modbus-baudrate": "ボーレート", + "modbus-databits": "データビット", + "modbus-stopbits": "ストップビット", + "modbus-databits-range": "データビットは7〜8の範囲内にある必要があります。", + "modbus-stopbits-range": "ストップビットは1〜2の範囲内でなければなりません。", + "modbus-unit-id": "ユニットID", + "modbus-unit-id-range": "ユニットIDは1〜247の範囲で指定してください。", + "modbus-device-name": "装置名", + "modbus-poll-period": "投票期間(ミリ秒)", + "modbus-attributes-poll-period": "属性のポーリング期間(ミリ秒)", + "modbus-timeseries-poll-period": "時系列ポーリング期間(ミリ秒)", + "modbus-poll-period-range": "投票期間は正の値でなければなりません。", + "modbus-tag": "タグ", + "modbus-function": "関数", + "modbus-register-address": "登録アドレス", + "modbus-register-address-range": "レジスタのアドレスは0〜65535の範囲内である必要があります。", + "modbus-register-bit-index": "ビットインデックス", + "modbus-register-bit-index-range": "ビットインデックスは0〜15の範囲内である必要があります。", + "modbus-register-count": "レジスタ数", + "modbus-register-count-range": "レジスタ数は正の値でなければなりません。", + "modbus-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": "アバター", + "open-user-menu": "ユーザーメニューを開く" + }, + "import": { + "no-file": "ファイルが選択されていません", + "drop-file": "JSONファイルをドロップするか、アップロードするファイルをクリックして選択します。" + }, + "item": { + "selected": "選択された" + }, + "js-func": { + "no-return-error": "関数は値を返す必要があります!", + "return-type-mismatch": "'{{type}}'タイプ!", + "tidy": "きちんとした" + }, + "key-val": { + "key": "キー", + "value": "値", + "remove-entry": "エントリを削除", + "add-entry": "エントリを追加", + "no-data": "エントリなし" + }, + "layout": { + "layout": "レイアウト", + "manage": "レイアウトの管理", + "settings": "レイアウト設定", + "color": "色", + "main": "メイン", + "right": "右", + "select": "ターゲットレイアウトを選択" + }, + "legend": { + "position": "伝説の位置", + "show-max": "最大値を表示", + "show-min": "最小値を表示する", + "show-avg": "平均値を表示", + "show-total": "合計値を表示", + "settings": "凡例の設定", + "min": "分", + "max": "最大", + "avg": "平均", + "total": "合計" + }, + "login": { + "login": "ログイン", + "request-password-reset": "リクエストパスワードのリセット", + "reset-password": "パスワードを再設定する", + "create-password": "パスワードの作成", + "passwords-mismatch-error": "入力されたパスワードは同じでなければなりません!", + "password-again": "パスワードをもう一度", + "sign-in": "サインインしてください", + "username": "ユーザー名(電子メール)", + "remember-me": "私を覚えてますか", + "forgot-password": "パスワードをお忘れですか?", + "password-reset": "パスワードのリセット", + "new-password": "新しいパスワード", + "new-password-again": "新しいパスワードを再入力", + "password-link-sent-message": "パスワードリセットリンクが正常に送信されました!", + "email": "Eメール" + }, + "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} other {# relations} }選択された", + "type": "タイプ", + "to-entity-type": "エンティティタイプへ", + "to-entity-name": "エンティティ名に", + "from-entity-type": "エンティティタイプから", + "from-entity-name": "エンティティ名から", + "to-entity": "実体へ", + "from-entity": "エンティティから", + "delete": "関係を削除する", + "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} other {# 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": "ルールチェーンIDのコピー", + "idCopiedMessage": "ルールチェーンIDがクリップボードにコピーされました", + "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": "選択を削除します", + "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-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": "助けて" + }, + "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": "テナントIDをコピーする", + "idCopiedMessage": "テナントIDがクリップボードにコピーされました", + "select-tenant": "テナントを選択", + "no-tenants-matching": "'{{entity}}'発見されました。", + "tenant-required": "テナントが必要です" + }, + "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": "ユーザー", + "customer-users": "顧客ユーザー", + "tenant-admins": "テナント管理者", + "sys-admin": "システム管理者", + "tenant-admin": "テナント管理者", + "customer": "顧客", + "anonymous": "匿名", + "add": "ユーザーを追加する", + "delete": "ユーザーを削除", + "add-user-text": "新しいユーザーを追加", + "no-users-text": "ユーザが見つかりませんでした", + "user-details": "ユーザーの詳細", + "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": "アクティベーション電子メールが正常に送信されました!", + "resend-activation": "アクティブ化を再送", + "email": "Eメール", + "email-required": "電子メールが必要です。", + "invalid-email-format": "メールフォーマットが無効です。", + "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": "アクティベーションメールを送信する", + "activation-link": "ユーザーアクティベーションリンク", + "activation-link-text": "activation link :", + "copy-activation-link": "アクティブ化リンクをコピーする", + "activation-link-copied-message": "ユーザーのアクティベーションリンクがクリップボードにコピーされました", + "details": "詳細" + }, + "value": { + "type": "値のタイプ", + "string": "文字列", + "string-value": "文字列値", + "integer": "整数", + "integer-value": "整数値", + "invalid-integer-value": "整数値が無効です", + "double": "ダブル", + "double-value": "二重価値", + "boolean": "ブール", + "boolean-value": "ブール値", + "false": "偽", + "true": "真", + "long": "長いです" + }, + "widget": { + "widget-library": "ウィジェットライブラリ", + "widget-bundle": "ウィジェットバンドル", + "select-widgets-bundle": "ウィジェットのバンドルを選択", + "management": "ウィジェット管理", + "editor": "ウィジェットエディタ", + "widget-type-not-found": "ウィジェットの設定を読み込む際に問題が発生しました。
おそらく関連付けられているウィジェットのタイプが削除されています。", + "widget-type-load-error": "次のエラーのためにウィジェットが読み込まれませんでした:", + "remove": "ウィジェットを削除", + "edit": "ウィジェットの編集", + "remove-widget-title": "'{{widgetTitle}}'?", + "remove-widget-text": "確認後、ウィジェットと関連するすべてのデータは回復不能になります。", + "timeseries": "時系列", + "search-data": "検索データ", + "no-data-found": "何もデータが見つかりませんでした", + "latest-values": "最新の値", + "rpc": "コントロールウィジェット", + "alarm": "アラームウィジェット", + "static": "静的ウィジェット", + "select-widget-type": "ウィジェットタイプを選択", + "missing-widget-title-error": "ウィジェットのタイトルを指定する必要があります!", + "widget-saved": "ウィジェットが保存されました", + "unable-to-save-widget-error": "ウィジェットを保存できません!ウィジェットにエラーがあります!", + "save": "ウィジェットを保存", + "saveAs": "ウィジェットを次のように保存する", + "save-widget-type-as": "ウィジェットタイプを次のように保存します", + "save-widget-type-as-text": "新しいウィジェットのタイトルを入力したり、ターゲットウィジェットのバンドルを選択してください", + "toggle-fullscreen": "フルスクリーン切り替え", + "run": "ウィジェットを実行する", + "title": "ウィジェットのタイトル", + "title-required": "ウィジェットのタイトルが必要です。", + "type": "ウィジェットタイプ", + "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": "'{{widgetName}}'?", + "remove-widget-type-text": "確認後、ウィジェットのタイプと関連するすべてのデータは回復不能になります。", + "remove-widget-type": "ウィジェットタイプを削除", + "add-widget-type": "新しいウィジェットタイプを追加する", + "widget-type-load-failed-error": "ウィジェットタイプの読み込みに失敗しました!", + "widget-template-load-failed-error": "ウィジェットテンプレートを読み込めませんでした!", + "add": "ウィジェットを追加", + "undo": "ウィジェットの変更を元に戻す", + "export": "ウィジェットの書き出し" + }, + "widget-action": { + "header-button": "ウィジェットのヘッダーボタン", + "open-dashboard-state": "新しいダッシュボードの状態に移動する", + "update-dashboard-state": "現在のダッシュボードの状態を更新する", + "open-dashboard": "他のダッシュボードに移動する", + "custom": "カスタムアクション", + "target-dashboard-state": "ターゲットダッシュボードの状態", + "target-dashboard-state-required": "ターゲットダッシュボードの状態が必要です", + "set-entity-from-widget": "エンティティをウィジェットから設定する", + "target-dashboard": "ターゲットダッシュボード", + "open-right-layout": "右ダッシュボードレイアウトを開く(モバイルビュー)" + }, + "widgets-bundle": { + "current": "現在のバンドル", + "widgets-bundles": "ウィジェットバンドル", + "add": "ウィジェットのバンドルを追加", + "delete": "ウィジェットのバンドルを削除する", + "title": "タイトル", + "title-required": "タイトルは必須です。", + "add-widgets-bundle-text": "新しいウィジェットのバンドルを追加する", + "no-widgets-bundles-text": "ウィジェットバンドルが見つかりません", + "empty": "ウィジェットのバンドルが空です", + "details": "詳細", + "widgets-bundle-details": "ウィジェットのバンドルの詳細", + "delete-widgets-bundle-title": "'{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "確認後、ウィジェットはバンドルされ、関連するすべてのデータは回復不能になります。", + "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": "確認後、選択したすべてのウィジェットバンドルは削除され、関連するすべてのデータは回復不能になります。", + "no-widgets-bundles-matching": "'{{widgetsBundle}}'発見されました。", + "widgets-bundle-required": "ウィジェットバンドルが必要です。", + "system": "システム", + "import": "インポートウィジェットバンドル", + "export": "ウィジェットのエクスポートバンドル", + "export-failed-error": "{{error}}", + "create-new-widgets-bundle": "新しいウィジェットバンドルを作成する", + "widgets-bundle-file": "ウィジェットのバンドルファイル", + "invalid-widgets-bundle-file-error": "ウィジェットをインポートできません。bundle:データ構造が無効です。" + }, + "widget-config": { + "data": "データ", + "settings": "設定", + "advanced": "上級", + "title": "タイトル", + "general-settings": "一般設定", + "display-title": "タイトルを表示", + "drop-shadow": "影を落とす", + "enable-fullscreen": "フルスクリーンを有効にする", + "background-color": "背景色", + "text-color": "テキストの色", + "padding": "パディング", + "margin": "マージン", + "widget-style": "ウィジェットスタイル", + "title-style": "タイトルスタイル", + "mobile-mode-settings": "モバイルモードの設定", + "order": "注文", + "height": "高さ", + "units": "値の隣に表示する特別なシンボル", + "decimals": "浮動小数点の後の桁数", + "timewindow": "タイムウィンドウ", + "use-dashboard-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": "ターゲットデバイス", + "alarm-source": "アラームソース", + "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": "ウィジェットアクションを削除する", + "delete-action-text": "'{{actionName}}'?" + }, + "widget-type": { + "import": "インポートウィジェットタイプ", + "export": "ウィジェットのタイプをエクスポートする", + "export-failed-error": "{{error}}", + "create-new-widget-type": "新しいウィジェットタイプを作成する", + "widget-type-file": "ウィジェットタイプファイル", + "invalid-widget-type-file-error": "ウィジェットタイプをインポートできません:ウィジェットタイプのデータ構造が無効です。" + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "日", + "Mon": "月", + "Tue": "火", + "Wed": "水", + "Thu": "木", + "Fri": "金", + "Sat": "土", + "Jan": "1月", + "Feb": "2月", + "Mar": "3月", + "Apr": "4月", + "May": "5月", + "Jun": "6月", + "Jul": "7月", + "Aug": "8月", + "Sep": "9月", + "Oct": "10月", + "Nov": "11月", + "Dec": "12月", + "January": "1月", + "February": "2月", + "March": "行進", + "April": "4月", + "June": "六月", + "July": "7月", + "August": "8月", + "September": "9月", + "October": "10月", + "November": "11月", + "December": "12月", + "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": "すべてのアイコンを表示する" + }, + "custom": { + "widget-action": { + "action-cell-button": "アクションセルボタン", + "row-click": "行のクリック", + "polygon-click": "ポリゴンクリック", + "marker-click": "マーカークリック", + "tooltip-tag-action": "ツールチップのタグアクション" + } + }, + "language": { + "language": "言語", + "locales": { + "de_DE": "ドイツ語", + "fr_FR": "フランス語", + "en_US": "英語", + "ko_KR": "韓国語", + "it_IT": "イタリアの", + "zh_CN": "中国語", + "ru_RU": "ロシア", + "es_ES": "スペイン語", + "ja_JA": "日本語", + "tr_TR": "トルコ語", + "fa_IR": "ペルシャ語", + "uk_UA": "ウクライナ語", + "cs_CZ": "チェコ語で" + } + } } \ No newline at end of file diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json index fde60c77bb..863a12e6de 100644 --- a/ui/src/app/locale/locale.constant-ko_KR.json +++ b/ui/src/app/locale/locale.constant-ko_KR.json @@ -83,6 +83,8 @@ "timeout-required": "제한시간을 입력해야 합니다.", "timeout-invalid": "올바른 제한시간이 아닙니다.", "enable-tls": "TLS 사용", + "tls-version" : "TLS 버전", + "enter-tls-version" : "TLS 버전을 입력하세요", "send-test-mail": "테스트 메일 보내기" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-lv_LV.json b/ui/src/app/locale/locale.constant-lv_LV.json index 4ddbb1856f..169777a31e 100644 --- a/ui/src/app/locale/locale.constant-lv_LV.json +++ b/ui/src/app/locale/locale.constant-lv_LV.json @@ -84,6 +84,8 @@ "timeout-required": "Timeout is required.", "timeout-invalid": "That doesn't look like a valid timeout.", "enable-tls": "Enable TLS", + "tls-version": "TLS version", + "enter-tls-version" : "Enter TLS version", "send-test-mail": "Send test mail" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 87075014ba..4ea9dac882 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -85,6 +85,8 @@ "timeout-required": "Таймаут обязателен.", "timeout-invalid": "Недействительный таймаут.", "enable-tls": "Включить TLS", + "tls-version" : "Версия TLS", + "enter-tls-version" : "Введите версию TLS", "send-test-mail": "Отправить пробное письмо", "security-settings": "Настройки безопасности", "password-policy": "Политика паролей", diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index c9c5964c52..1f5b424faf 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -83,6 +83,8 @@ "timeout-required": "Zaman aşımı değeri gerekli.", "timeout-invalid": "Bu geçerli bir zaman aşımı gibi görünmüyor.", "enable-tls": "TLS'i etkinleştir.", + "tls-version" : "TLS sürümü", + "enter-tls-version" : "TLS sürümünü girin", "send-test-mail": "Test e-postası gönder" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 6aa1e06e89..f1f4f981f5 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -87,6 +87,8 @@ "timeout-required": "Необхідно задати час очікування.", "timeout-invalid": "Це не схоже на правильний час очікування.", "enable-tls": "Увімкнути TLS", + "tls-version" : "Версія TLS", + "enter-tls-version" : "Вкажіть версію TLS", "send-test-mail": "Надіслати тестове повідомлення", "use-system-mail-settings": "Використовувати параметри системного поштового сервера", "mail-templates": "Шаблони електронної пошти", diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index 0c1f5f743b..d3fef3f96a 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -83,6 +83,8 @@ "timeout-required": "超时必填。", "timeout-invalid": "这看起来不像有效的超时值。", "enable-tls": "启用TLS", + "tls-version" : "TLS版本", + "enter-tls-version" : "输入TLS版本", "send-test-mail": "发送测试邮件" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-zh_TW.json b/ui/src/app/locale/locale.constant-zh_TW.json index acaa9ac53c..1444cb5cc2 100644 --- a/ui/src/app/locale/locale.constant-zh_TW.json +++ b/ui/src/app/locale/locale.constant-zh_TW.json @@ -83,6 +83,8 @@         "timeout-required": "超時必填。",         "timeout-invalid": "這看起來不像有效的超時值。",         "enable-tls": "啟用TLS", + "tls-version": "TLS版本", + "enter-tls-version" : "输入TLS版本",         "send-test-mail": "發送測試郵件"     },     "alarm": { From 436d37ff42a1d01d5ed5a5db68d83889de777623 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Sat, 15 Feb 2020 13:37:52 +0200 Subject: [PATCH 193/261] Refactoring and backward-compatiblity improvements --- .../device/DeviceActorMessageProcessor.java | 32 ++--------- .../service/mail/DefaultMailService.java | 9 ++- .../thingsboard/server/utils/JsonUtils.java | 55 +++++++++++++++++++ 3 files changed, 67 insertions(+), 29 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/utils/JsonUtils.java 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 1be7e18aab..acf1a3161c 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 @@ -69,6 +69,7 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +import org.thingsboard.server.utils.JsonUtils; import javax.annotation.Nullable; import java.util.ArrayList; @@ -102,7 +103,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private final Map toServerRpcPendingMap; private final Gson gson = new Gson(); - private final JsonParser jsonParser = new JsonParser(); private int rpcSeq = 0; private String deviceName; @@ -327,7 +327,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) { - JsonObject json = getJsonObject(postAttributes.getKvList()); + JsonObject json = JsonUtils.getJsonObject(postAttributes.getKvList()); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); pushToRuleEngine(context, tbMsg); @@ -335,7 +335,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) { for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) { - JsonObject json = getJsonObject(tsKv.getKvList()); + JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); TbMsgMetaData metaData = defaultMetaData.copy(); metaData.putValue("ts", tsKv.getTs() + ""); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); @@ -347,7 +347,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { UUID sessionId = getSessionId(sessionInfo); JsonObject json = new JsonObject(); json.addProperty("method", request.getMethodName()); - json.add("params", jsonParser.parse(request.getParams())); + json.add("params", JsonUtils.parse(request.getParams())); TbMsgMetaData requestMetaData = defaultMetaData.copy(); requestMetaData.putValue("requestId", Integer.toString(request.getRequestId())); @@ -551,30 +551,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { this.defaultMetaData.putValue("deviceType", deviceType); } - private JsonObject getJsonObject(List tsKv) { - JsonObject json = new JsonObject(); - for (KeyValueProto kv : tsKv) { - switch (kv.getType()) { - case BOOLEAN_V: - json.addProperty(kv.getKey(), kv.getBoolV()); - break; - case LONG_V: - json.addProperty(kv.getKey(), kv.getLongV()); - break; - case DOUBLE_V: - json.addProperty(kv.getKey(), kv.getDoubleV()); - break; - case STRING_V: - json.addProperty(kv.getKey(), kv.getStringV()); - break; - case JSON_V: - json.add(kv.getKey(), jsonParser.parse(kv.getJsonV())); - break; - } - } - return json; - } - private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) { DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() .setSessionIdMSB(sessionInfo.getSessionIdMSB()) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 9d2ae3beac..baa0d417a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -103,7 +103,14 @@ public class DefaultMailService implements MailService { javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText()))); - boolean enableTls = jsonConfig.has("enableTls") && jsonConfig.get("enableTls").booleanValue(); + boolean enableTls = false; + if (jsonConfig.has("enableTls")) { + if (jsonConfig.get("enableTls").isBoolean() && jsonConfig.get("enableTls").booleanValue()) { + enableTls = true; + } else if (jsonConfig.get("enableTls").isTextual()) { + enableTls = "true".equalsIgnoreCase(jsonConfig.get("enableTls").asText()); + } + } javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls); if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) { javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); diff --git a/application/src/main/java/org/thingsboard/server/utils/JsonUtils.java b/application/src/main/java/org/thingsboard/server/utils/JsonUtils.java new file mode 100644 index 0000000000..5621362c09 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/JsonUtils.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2020 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.utils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; +import java.util.List; + +public class JsonUtils { + + private static final JsonParser jsonParser = new JsonParser(); + + public static JsonObject getJsonObject(List tsKv) { + JsonObject json = new JsonObject(); + for (KeyValueProto kv : tsKv) { + switch (kv.getType()) { + case BOOLEAN_V: + json.addProperty(kv.getKey(), kv.getBoolV()); + break; + case LONG_V: + json.addProperty(kv.getKey(), kv.getLongV()); + break; + case DOUBLE_V: + json.addProperty(kv.getKey(), kv.getDoubleV()); + break; + case STRING_V: + json.addProperty(kv.getKey(), kv.getStringV()); + break; + case JSON_V: + json.add(kv.getKey(), jsonParser.parse(kv.getJsonV())); + break; + } + } + return json; + } + + public static JsonElement parse(String params) { + return jsonParser.parse(params); + } +} From a2b7e1c098eccff6ae643d7259ff715483434597 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Mon, 17 Feb 2020 18:11:09 +0200 Subject: [PATCH 194/261] DB upgrade script for JSON support feature * Added upgrade * Added upgrade call for attributes from ThingsboardInstallService * refactored cassandra upgrade services --- .../install/ThingsboardInstallService.java | 1 + ...stractCassandraDatabaseUpgradeService.java | 48 ++++++++++++++++ .../CassandraDatabaseUpgradeService.java | 47 +++++++--------- .../CassandraTsDatabaseUpgradeService.java | 56 +++++++++++++++++++ .../install/PsqlTsDatabaseUpgradeService.java | 3 + .../install/SqlDatabaseUpgradeService.java | 10 ++++ .../TimescaleTsDatabaseUpgradeService.java | 3 + 7 files changed, 140 insertions(+), 28 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java 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 14d9ff821f..7ef4363e77 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -136,6 +136,7 @@ public class ThingsboardInstallService { log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); databaseTsUpgradeService.upgradeDatabase("2.4.3"); + databaseEntitiesUpgradeService.upgradeDatabase("2.4.3"); log.info("Updating system data..."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java new file mode 100644 index 0000000000..603158314a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2020 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.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; +import org.thingsboard.server.service.install.cql.CQLStatementsParser; + +import java.nio.file.Path; +import java.util.List; + +@Slf4j +public abstract class AbstractCassandraDatabaseUpgradeService { + @Autowired + protected CassandraCluster cluster; + + @Autowired + @Qualifier("CassandraInstallCluster") + private CassandraInstallCluster installCluster; + + protected void loadCql(Path cql) throws Exception { + List statements = new CQLStatementsParser(cql).getStatements(); + statements.forEach(statement -> { + installCluster.getSession().execute(statement); + try { + Thread.sleep(2500); + } catch (InterruptedException e) { + } + }); + Thread.sleep(5000); + } +} 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 7e05179be0..721d43bf9f 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 @@ -19,20 +19,15 @@ import com.datastax.driver.core.KeyspaceMetadata; import com.datastax.driver.core.exceptions.InvalidQueryException; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.cassandra.CassandraCluster; -import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.util.NoSqlDao; -import org.thingsboard.server.service.install.cql.CQLStatementsParser; import org.thingsboard.server.service.install.cql.CassandraDbHelper; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ASSET; @@ -59,17 +54,10 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE; @NoSqlDao @Profile("install") @Slf4j -public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { +public class CassandraDatabaseUpgradeService extends AbstractCassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; - @Autowired - private CassandraCluster cluster; - - @Autowired - @Qualifier("CassandraInstallCluster") - private CassandraInstallCluster installCluster; - @Autowired private DashboardService dashboardService; @@ -264,7 +252,8 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS try { cluster.getSession().execute(updateDeviceTableStmt); Thread.sleep(2500); - } catch (InvalidQueryException e) {} + } catch (InvalidQueryException e) { + } log.info("Schema updated."); break; case "2.4.1": @@ -275,7 +264,8 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS cluster.getSession().execute(updateAssetTableStmt); Thread.sleep(2500); log.info("Assets updated."); - } catch (InvalidQueryException e) {} + } catch (InvalidQueryException e) { + } log.info("Schema updated."); break; case "2.4.2": @@ -286,24 +276,25 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS cluster.getSession().execute(updateAlarmTableStmt); Thread.sleep(2500); log.info("Alarms updated."); - } catch (InvalidQueryException e) {} + } catch (InvalidQueryException e) { + } + log.info("Schema updated."); + break; + case "2.4.3": + log.info("Updating schema ..."); + String updateAttributeKvTableStmt = "alter table attributes_kv_cf add json_v text"; + try { + log.info("Updating attributes ..."); + cluster.getSession().execute(updateAttributeKvTableStmt); + Thread.sleep(2500); + log.info("Attributes updated."); + } catch (InvalidQueryException e) { + } log.info("Schema updated."); break; default: throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); } - - } - - private void loadCql(Path cql) throws Exception { - List statements = new CQLStatementsParser(cql).getStatements(); - statements.forEach(statement -> { - installCluster.getSession().execute(statement); - try { - Thread.sleep(2500); - } catch (InterruptedException e) {} - }); - Thread.sleep(5000); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java new file mode 100644 index 0000000000..4bc68e92bc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2020 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.install; + +import com.datastax.driver.core.exceptions.InvalidQueryException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.NoSqlTsDao; + +@Service +@NoSqlTsDao +@Profile("install") +@Slf4j +public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabaseUpgradeService implements DatabaseTsUpgradeService { + + @Override + public void upgradeDatabase(String fromVersion) throws Exception { + switch (fromVersion) { + case "2.4.3": + log.info("Updating schema ..."); + String updateTsKvTableStmt = "alter table ts_kv_cf add json_v text"; + String updateTsKvLatestTableStmt = "alter table ts_kv_latest_cf add json_v text"; + + try { + log.info("Updating ts ..."); + cluster.getSession().execute(updateTsKvTableStmt); + Thread.sleep(2500); + log.info("Ts updated."); + log.info("Updating ts latest ..."); + cluster.getSession().execute(updateTsKvLatestTableStmt); + Thread.sleep(2500); + log.info("Ts latest updated."); + } 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/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index 2b1cbd053d..eb951ed9ae 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -101,6 +101,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); + executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); + log.info("schema timeseries updated!"); } } 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 d018c7fcef..aa5d3e3d95 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 @@ -207,6 +207,16 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService log.info("Schema updated."); } break; + case "2.4.3": + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + log.info("Updating schema ..."); + try { + conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;"); + } catch (Exception e) { + } + log.info("Schema updated."); + } + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 84adbbc140..0c57e2e9bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -104,6 +104,9 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); + executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); + log.info("schema timeseries updated!"); } } From ec4362a8d29a4db303e5f4d9c2991c1ee3dd2c42 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 17 Feb 2020 12:00:37 +0200 Subject: [PATCH 195/261] added list with tls versions for outgoing-mail-settings --- ui/src/app/admin/admin.controller.js | 2 ++ ui/src/app/admin/outgoing-mail-settings.tpl.html | 6 +++++- ui/src/app/locale/locale.constant-cs_CZ.json | 1 - ui/src/app/locale/locale.constant-de_DE.json | 1 - ui/src/app/locale/locale.constant-el_GR.json | 1 - ui/src/app/locale/locale.constant-en_US.json | 1 - ui/src/app/locale/locale.constant-es_ES.json | 1 - ui/src/app/locale/locale.constant-fa_IR.json | 1 - ui/src/app/locale/locale.constant-fr_FR.json | 1 - ui/src/app/locale/locale.constant-it_IT.json | 1 - ui/src/app/locale/locale.constant-ja_JA.json | 1 - ui/src/app/locale/locale.constant-ko_KR.json | 1 - ui/src/app/locale/locale.constant-lv_LV.json | 1 - ui/src/app/locale/locale.constant-ru_RU.json | 1 - ui/src/app/locale/locale.constant-tr_TR.json | 1 - ui/src/app/locale/locale.constant-uk_UA.json | 1 - ui/src/app/locale/locale.constant-zh_CN.json | 1 - ui/src/app/locale/locale.constant-zh_TW.json | 1 - 18 files changed, 7 insertions(+), 17 deletions(-) diff --git a/ui/src/app/admin/admin.controller.js b/ui/src/app/admin/admin.controller.js index 256faf0245..fa1c9920ee 100644 --- a/ui/src/app/admin/admin.controller.js +++ b/ui/src/app/admin/admin.controller.js @@ -25,6 +25,8 @@ export default function AdminController(adminService, toast, $scope, $rootScope, return protocol; }); + vm.tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; + $translate('admin.test-mail-sent').then(function (translation) { vm.testMailSent = translation; }, function (translationId) { diff --git a/ui/src/app/admin/outgoing-mail-settings.tpl.html b/ui/src/app/admin/outgoing-mail-settings.tpl.html index 20f988f866..edb2e4e520 100644 --- a/ui/src/app/admin/outgoing-mail-settings.tpl.html +++ b/ui/src/app/admin/outgoing-mail-settings.tpl.html @@ -82,7 +82,11 @@ aria-label="{{ 'admin.enable-tls' | translate }}" ng-model="vm.settings.jsonValue.enableTls">{{ 'admin.enable-tls' | translate }} - + + + {{tlsVersion}} + + diff --git a/ui/src/app/locale/locale.constant-cs_CZ.json b/ui/src/app/locale/locale.constant-cs_CZ.json index 59d6fd5ece..76311883a8 100644 --- a/ui/src/app/locale/locale.constant-cs_CZ.json +++ b/ui/src/app/locale/locale.constant-cs_CZ.json @@ -84,7 +84,6 @@ "timeout-invalid": "Tohle nevypadá jako platný časový limit.", "enable-tls": "Povolit TLS", "tls-version": "Verze TLS", - "enter-tls-version" : "Zadejte verzi TLS", "send-test-mail": "Odeslat testovací zprávu" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json index 7fb595a882..57d64c104e 100644 --- a/ui/src/app/locale/locale.constant-de_DE.json +++ b/ui/src/app/locale/locale.constant-de_DE.json @@ -84,7 +84,6 @@ "timeout-invalid": "Das ist keine gültige Wartezeit.", "enable-tls": "TLS aktivieren", "tls-version" : "TLS-Version", - "enter-tls-version" : "Geben Sie die TLS-Version ein", "send-test-mail": "Test E-Mail senden", "security-settings": "Sicherheitseinstellungen", "password-policy": "Kennwortrichtlinie", diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index 7669e705b9..1817c64509 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -89,7 +89,6 @@ "timeout-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη τιμή timeout.", "enable-tls": "Ενεργοποίηση TLS", "tls-version": "Έκδοση TLS", - "enter-tls-version" : "Εισαγάγετε την έκδοση TLS", "send-test-mail": "Αποστολή δοκιμαστικού μηνύματος", "use-system-mail-settings": "Χρήση των ρυθμίσεων διακομιστή αλληλογραφίας συστήματος", "mail-templates": "Πρότυπα αλληλογραφίας", diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index cfadab8e1f..52baeda729 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -87,7 +87,6 @@ "timeout-invalid": "That doesn't look like a valid timeout.", "enable-tls": "Enable TLS", "tls-version": "TLS version", - "enter-tls-version" : "Enter TLS version", "send-test-mail": "Send test mail", "security-settings": "Security settings", "password-policy": "Password policy", diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json index 4b76cd7b70..0473b7df8f 100644 --- a/ui/src/app/locale/locale.constant-es_ES.json +++ b/ui/src/app/locale/locale.constant-es_ES.json @@ -86,7 +86,6 @@ "timeout-invalid": "Eso no parece un tiempo de espera válido.", "enable-tls": "Habilitar TLS", "tls-version": "Versión TLS", - "enter-tls-version" : "Ingrese la versión de TLS", "send-test-mail": "Enviar correo de prueba", "password-policy": "Política de contraseñas", "security-settings": "Configuraciones de seguridad", diff --git a/ui/src/app/locale/locale.constant-fa_IR.json b/ui/src/app/locale/locale.constant-fa_IR.json index 968769fe29..4c83a7eea9 100644 --- a/ui/src/app/locale/locale.constant-fa_IR.json +++ b/ui/src/app/locale/locale.constant-fa_IR.json @@ -84,7 +84,6 @@ "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد", "enable-tls": "TLS فعال سازي", "tls-version": "نسخه TLS", - "enter-tls-version" : "نسخه TLS را وارد کنید", "send-test-mail": "ارسال پيام آزمايشي" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json index c12418652a..f155563742 100644 --- a/ui/src/app/locale/locale.constant-fr_FR.json +++ b/ui/src/app/locale/locale.constant-fr_FR.json @@ -57,7 +57,6 @@ "base-url-required": "L'URL de base est requise.", "enable-tls": "Activer TLS", "tls-version": "Version TLS", - "enter-tls-version" : "Entrez la version TLS", "general": "Général", "general-settings": "Paramètres généraux", "mail-from": "Mail de", diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json index 8b53c91983..f3a6be8d96 100644 --- a/ui/src/app/locale/locale.constant-it_IT.json +++ b/ui/src/app/locale/locale.constant-it_IT.json @@ -85,7 +85,6 @@ "timeout-invalid": "Timeout non valido.", "enable-tls": "Abilita TLS", "tls-version" : "Versione TLS", - "enter-tls-version" : "Inserisci la versione TLS", "send-test-mail": "Invia mail di test", "security-settings": "Settaggi di sicurezza", "password-policy": "Politica password", diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json index 6e60c0ed95..4a789094f5 100644 --- a/ui/src/app/locale/locale.constant-ja_JA.json +++ b/ui/src/app/locale/locale.constant-ja_JA.json @@ -84,7 +84,6 @@ "timeout-invalid": "それは有効なタイムアウトのようには見えません。", "enable-tls": "TLSを有効にする", "tls-version": "TLSバージョン", - "enter-tls-version" : "TLSバージョンを入力してください", "send-test-mail": "テストメールを送信する" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json index 863a12e6de..f9395b1dd6 100644 --- a/ui/src/app/locale/locale.constant-ko_KR.json +++ b/ui/src/app/locale/locale.constant-ko_KR.json @@ -84,7 +84,6 @@ "timeout-invalid": "올바른 제한시간이 아닙니다.", "enable-tls": "TLS 사용", "tls-version" : "TLS 버전", - "enter-tls-version" : "TLS 버전을 입력하세요", "send-test-mail": "테스트 메일 보내기" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-lv_LV.json b/ui/src/app/locale/locale.constant-lv_LV.json index 169777a31e..08e7d34b7f 100644 --- a/ui/src/app/locale/locale.constant-lv_LV.json +++ b/ui/src/app/locale/locale.constant-lv_LV.json @@ -85,7 +85,6 @@ "timeout-invalid": "That doesn't look like a valid timeout.", "enable-tls": "Enable TLS", "tls-version": "TLS version", - "enter-tls-version" : "Enter TLS version", "send-test-mail": "Send test mail" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 4ea9dac882..f4ed6d5c93 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -86,7 +86,6 @@ "timeout-invalid": "Недействительный таймаут.", "enable-tls": "Включить TLS", "tls-version" : "Версия TLS", - "enter-tls-version" : "Введите версию TLS", "send-test-mail": "Отправить пробное письмо", "security-settings": "Настройки безопасности", "password-policy": "Политика паролей", diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json index 1f5b424faf..a9dd8e7697 100644 --- a/ui/src/app/locale/locale.constant-tr_TR.json +++ b/ui/src/app/locale/locale.constant-tr_TR.json @@ -84,7 +84,6 @@ "timeout-invalid": "Bu geçerli bir zaman aşımı gibi görünmüyor.", "enable-tls": "TLS'i etkinleştir.", "tls-version" : "TLS sürümü", - "enter-tls-version" : "TLS sürümünü girin", "send-test-mail": "Test e-postası gönder" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index f1f4f981f5..53d16bea5e 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -88,7 +88,6 @@ "timeout-invalid": "Це не схоже на правильний час очікування.", "enable-tls": "Увімкнути TLS", "tls-version" : "Версія TLS", - "enter-tls-version" : "Вкажіть версію TLS", "send-test-mail": "Надіслати тестове повідомлення", "use-system-mail-settings": "Використовувати параметри системного поштового сервера", "mail-templates": "Шаблони електронної пошти", diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json index d3fef3f96a..634aae3f1f 100644 --- a/ui/src/app/locale/locale.constant-zh_CN.json +++ b/ui/src/app/locale/locale.constant-zh_CN.json @@ -84,7 +84,6 @@ "timeout-invalid": "这看起来不像有效的超时值。", "enable-tls": "启用TLS", "tls-version" : "TLS版本", - "enter-tls-version" : "输入TLS版本", "send-test-mail": "发送测试邮件" }, "alarm": { diff --git a/ui/src/app/locale/locale.constant-zh_TW.json b/ui/src/app/locale/locale.constant-zh_TW.json index 1444cb5cc2..52637b7c82 100644 --- a/ui/src/app/locale/locale.constant-zh_TW.json +++ b/ui/src/app/locale/locale.constant-zh_TW.json @@ -84,7 +84,6 @@         "timeout-invalid": "這看起來不像有效的超時值。",         "enable-tls": "啟用TLS", "tls-version": "TLS版本", - "enter-tls-version" : "输入TLS版本",         "send-test-mail": "發送測試郵件"     },     "alarm": { From 90e84b77ebd086977741187fdc9ee096f4a45e4a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 17 Feb 2020 19:05:30 +0200 Subject: [PATCH 196/261] Fix for Cassandra Dockerfile --- msa/tb/docker-cassandra/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index d1dfc21973..27b95a4c27 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -19,7 +19,7 @@ FROM thingsboard/openjdk8 RUN apt-get update RUN apt-get install -y curl nmap procps RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null -RUN curl https://www.apache.org/dist/cassandra/KEYS | apt-key add - +RUN wget -qO - https://www.apache.org/dist/cassandra/KEYS | apt-key add - RUN apt-get update RUN apt-get install -y cassandra cassandra-tools RUN update-rc.d cassandra disable From edb21ae0eba9dae5c46fd27f08d96e0fdc38a2fb Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 17 Feb 2020 19:47:07 +0200 Subject: [PATCH 197/261] Dockerfile fix --- msa/tb/docker-cassandra/Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 27b95a4c27..f8509487b3 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -17,6 +17,7 @@ FROM thingsboard/openjdk8 RUN apt-get update +RUN apt-get install apt-transport-https ca-certificates RUN apt-get install -y curl nmap procps RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null RUN wget -qO - https://www.apache.org/dist/cassandra/KEYS | apt-key add - From a32e8b7342aa3b1e2b048e8a9b7b026980bd435a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 17 Feb 2020 20:05:58 +0200 Subject: [PATCH 198/261] Dockerfile fix --- msa/tb/docker-cassandra/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index f8509487b3..9f87819909 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -17,7 +17,7 @@ FROM thingsboard/openjdk8 RUN apt-get update -RUN apt-get install apt-transport-https ca-certificates +RUN apt-get install -y apt-transport-https ca-certificates RUN apt-get install -y curl nmap procps RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null RUN wget -qO - https://www.apache.org/dist/cassandra/KEYS | apt-key add - From 4c3d6a3c87ae8c25429c9a049ebed033017ea2a0 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 18 Feb 2020 10:07:46 +0200 Subject: [PATCH 199/261] Fixed Cassandra installation script --- msa/tb/docker-cassandra/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 9f87819909..3a761398fd 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -17,10 +17,9 @@ FROM thingsboard/openjdk8 RUN apt-get update -RUN apt-get install -y apt-transport-https ca-certificates RUN apt-get install -y curl nmap procps RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null -RUN wget -qO - https://www.apache.org/dist/cassandra/KEYS | apt-key add - +RUN curl -L https://www.apache.org/dist/cassandra/KEYS | apt-key add - RUN apt-get update RUN apt-get install -y cassandra cassandra-tools RUN update-rc.d cassandra disable From a670b77251f09e6d4b9c9aa197cf0e8a64ac916a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 19 Feb 2020 11:35:21 +0200 Subject: [PATCH 200/261] refactored and improvement Rest Client --- .../thingsboard/client/tools/RestClient.java | 549 +++++++++--------- 1 file changed, 268 insertions(+), 281 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 179902d1d3..91d9e1bfec 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -45,10 +45,14 @@ 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.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetSearchQuery; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; @@ -57,6 +61,14 @@ 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.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.TextPageData; @@ -64,9 +76,11 @@ 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.plugin.ComponentType; 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.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -87,6 +101,7 @@ import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.stream.Collectors; import static org.springframework.util.StringUtils.isEmpty; @@ -106,8 +121,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; public RestClient(String baseURL) { - this.restTemplate = new RestTemplate(); - this.baseURL = baseURL; + this(new RestTemplate(), baseURL); } public RestClient(RestTemplate restTemplate, String baseURL) { @@ -279,18 +293,6 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); } - public void deleteCustomer(CustomerId customerId) { - restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId); - } - - public void deleteDevice(DeviceId deviceId) { - restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId); - } - - public void deleteAsset(AssetId assetId) { - restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId); - } - public Device assignDevice(CustomerId customerId, DeviceId deviceId) { return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId.toString(), deviceId.toString()).getBody(); @@ -313,10 +315,6 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { 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 = @@ -391,9 +389,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getAlarmById(String alarmId) { + public Optional getAlarmById(AlarmId alarmId) { try { - ResponseEntity alarm = restTemplate.getForEntity(baseURL + "/api/alarm/{alarmId}", Alarm.class, alarmId); + ResponseEntity alarm = restTemplate.getForEntity(baseURL + "/api/alarm/{alarmId}", Alarm.class, alarmId.getId()); return Optional.ofNullable(alarm.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -404,9 +402,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getAlarmInfoById(String alarmId) { + public Optional getAlarmInfoById(AlarmId alarmId) { try { - ResponseEntity alarmInfo = restTemplate.getForEntity(baseURL + "/api/alarm/info/{alarmId}", AlarmInfo.class, alarmId); + ResponseEntity alarmInfo = restTemplate.getForEntity(baseURL + "/api/alarm/info/{alarmId}", AlarmInfo.class, alarmId.getId()); return Optional.ofNullable(alarmInfo.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -421,70 +419,42 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); } - public void deleteAlarm(String alarmId) { - restTemplate.delete(baseURL + "/api/alarm/{alarmId}", alarmId); + public void deleteAlarm(AlarmId alarmId) { + restTemplate.delete(baseURL + "/api/alarm/{alarmId}", alarmId.getId()); } - public void ackAlarm(String alarmId) { - restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/ack", null, alarmId); + public void ackAlarm(AlarmId alarmId) { + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/ack", null, alarmId.getId()); } - public void clearAlarm(String alarmId) { - restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId); + public void clearAlarm(AlarmId alarmId) { + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); } - public TimePageData getAlarms(EntityId entityId, String searchStatus, String status, TimePageLink pageLink, Boolean fetchOriginator) { + public TimePageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); - params.put("searchStatus", searchStatus); - params.put("status", status); + params.put("searchStatus", searchStatus.name()); + params.put("status", status.name()); 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}&" + 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}"; - if (!isEmpty(pageLink.getTextSearch())) { - urlParams += "&textSearch={textSearch}"; - } - if (!isEmpty(pageLink.getIdOffset())) { - urlParams += "&idOffset={idOffset}"; - } - if (!isEmpty(pageLink.getTextOffset())) { - urlParams += "&textOffset={textOffset}"; - } - return urlParams; + }, + params).getBody(); } - public Optional getHighestAlarmSeverity(EntityId entityId, String searchStatus, String status) { + public Optional getHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); - params.put("searchStatus", searchStatus); - params.put("status", status); + params.put("searchStatus", searchStatus.name()); + params.put("status", status.name()); try { ResponseEntity alarmSeverity = restTemplate.getForEntity(baseURL + "/api/alarm/highestSeverity/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}", AlarmSeverity.class, params); return Optional.ofNullable(alarmSeverity.getBody()); @@ -497,9 +467,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getAssetById(String assetId) { + public Optional getAssetById(AssetId assetId) { try { - ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId); + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId.getId()); return Optional.ofNullable(asset.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -514,15 +484,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); } - public void deleteAsset(String assetId) { - restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId); + public void deleteAsset(AssetId assetId) { + restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId.getId()); } - public Optional assignAssetToCustomer(String customerId, - String assetId) { + public Optional assignAssetToCustomer(CustomerId customerId, AssetId assetId) { Map params = new HashMap<>(); - params.put("customerId", customerId); - params.put("assetId", assetId); + params.put("customerId", customerId.getId().toString()); + params.put("assetId", assetId.getId().toString()); try { ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, params); @@ -536,9 +505,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional unassignAssetFromCustomer(String assetId) { + public Optional unassignAssetFromCustomer(AssetId assetId) { try { - ResponseEntity asset = restTemplate.exchange(baseURL + "/api/customer/asset/{assetId}", HttpMethod.DELETE, HttpEntity.EMPTY, Asset.class, assetId); + ResponseEntity asset = restTemplate.exchange(baseURL + "/api/customer/asset/{assetId}", HttpMethod.DELETE, HttpEntity.EMPTY, Asset.class, assetId.getId()); return Optional.ofNullable(asset.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -549,9 +518,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional assignAssetToPublicCustomer(String assetId) { + public Optional assignAssetToPublicCustomer(AssetId assetId) { try { - ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/public/asset/{assetId}", null, Asset.class, assetId); + ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/public/asset/{assetId}", null, Asset.class, assetId.getId()); return Optional.ofNullable(asset.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -562,9 +531,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TextPageData getTenantAssets(TextPageLink pageLink, String type) { + public TextPageData getTenantAssets(TextPageLink pageLink, String assetType) { Map params = new HashMap<>(); - params.put("type", type); + params.put("type", assetType); addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( @@ -589,10 +558,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TextPageData getCustomerAssets(String customerId, TextPageLink pageLink, String type) { + public TextPageData getCustomerAssets(CustomerId customerId, TextPageLink pageLink, String assetType) { Map params = new HashMap<>(); - params.put("customerId", customerId); - params.put("type", type); + params.put("customerId", customerId.getId().toString()); + params.put("type", assetType); addPageLinkToParam(params, pageLink); ResponseEntity> assets = restTemplate.exchange( @@ -605,14 +574,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return assets.getBody(); } - public List getAssetsByIds(List assetIds) { + public List getAssetsByIds(List assetIds) { return restTemplate.exchange( baseURL + "/api/assets?assetIds={assetIds}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - listToString(assetIds)).getBody(); + listIdsToString(assetIds)) + .getBody(); } public List findByQuery(AssetSearchQuery query) { @@ -633,10 +603,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public TimePageData getAuditLogsByCustomerId(String customerId, TimePageLink pageLink, String actionTypes) { + public TimePageData getAuditLogsByCustomerId(CustomerId customerId, TimePageLink pageLink, List actionTypes) { Map params = new HashMap<>(); - params.put("customerId", customerId); - params.put("actionTypes", actionTypes); + params.put("customerId", customerId.getId().toString()); + params.put("actionTypes", listEnumToString(actionTypes)); addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( @@ -649,10 +619,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return auditLog.getBody(); } - public TimePageData getAuditLogsByUserId(String userId, TimePageLink pageLink, String actionTypes) { + public TimePageData getAuditLogsByUserId(UserId userId, TimePageLink pageLink, List actionTypes) { Map params = new HashMap<>(); - params.put("userId", userId); - params.put("actionTypes", actionTypes); + params.put("userId", userId.getId().toString()); + params.put("actionTypes", listEnumToString(actionTypes)); addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( @@ -665,11 +635,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return auditLog.getBody(); } - public TimePageData getAuditLogsByEntityId(EntityId entityId, String actionTypes, TimePageLink pageLink) { + public TimePageData getAuditLogsByEntityId(EntityId entityId, List actionTypes, TimePageLink pageLink) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); - params.put("actionTypes", actionTypes); + params.put("actionTypes", listEnumToString(actionTypes)); addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( @@ -682,9 +652,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return auditLog.getBody(); } - public TimePageData getAuditLogs(TimePageLink pageLink, String actionTypes) { + public TimePageData getAuditLogs(TimePageLink pageLink, List actionTypes) { Map params = new HashMap<>(); - params.put("actionTypes", actionTypes); + params.put("actionTypes", listEnumToString(actionTypes)); addPageLinkToParam(params, pageLink); ResponseEntity> auditLog = restTemplate.exchange( @@ -697,7 +667,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return auditLog.getBody(); } - public String getActivateToken(String userId) { + public String getActivateToken(UserId userId) { String activationLink = getActivationLink(userId); return StringUtils.delete(activationLink, baseURL + ACTIVATE_TOKEN_REGEX); } @@ -731,7 +701,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public ResponseEntity checkActivateToken(String userId) { + public ResponseEntity checkActivateToken(UserId userId) { String activateToken = getActivateToken(userId); return restTemplate.getForEntity(baseURL + "/api/noauth/activate?activateToken={activateToken}", String.class, activateToken); } @@ -742,7 +712,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest); } - public Optional activateUser(String userId, String password) { + public Optional activateUser(UserId userId, String password) { ObjectNode activateRequest = objectMapper.createObjectNode(); activateRequest.put("activateToken", getActivateToken(userId)); activateRequest.put("password", password); @@ -771,7 +741,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public List getComponentDescriptorsByType(String componentType) { + public List getComponentDescriptorsByType(ComponentType componentType) { return restTemplate.exchange( baseURL + "/api/components?componentType={componentType}", HttpMethod.GET, HttpEntity.EMPTY, @@ -780,19 +750,20 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { componentType).getBody(); } - public List getComponentDescriptorsByTypes(List componentTypes) { + public List getComponentDescriptorsByTypes(List componentTypes) { return restTemplate.exchange( baseURL + "/api/components?componentTypes={componentTypes}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { }, - listToString(componentTypes)).getBody(); + listEnumToString(componentTypes)) + .getBody(); } - public Optional getCustomerById(String customerId) { + public Optional getCustomerById(CustomerId customerId) { try { - ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}", Customer.class, customerId); + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}", Customer.class, customerId.getId()); return Optional.ofNullable(customer.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -803,9 +774,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getShortCustomerInfoById(String customerId) { + public Optional getShortCustomerInfoById(CustomerId customerId) { try { - ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}/shortInfo", JsonNode.class, customerId); + ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}/shortInfo", JsonNode.class, customerId.getId()); return Optional.ofNullable(customerInfo.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -816,16 +787,16 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public String getCustomerTitleById(String customerId) { - return restTemplate.getForObject(baseURL + "/api/customer/{customerId}/title", String.class, customerId); + public String getCustomerTitleById(CustomerId customerId) { + return restTemplate.getForObject(baseURL + "/api/customer/{customerId}/title", String.class, customerId.getId()); } public Customer saveCustomer(Customer customer) { return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); } - public void deleteCustomer(String customerId) { - restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId); + public void deleteCustomer(CustomerId customerId) { + restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId.getId()); } public TextPageData getCustomers(TextPageLink pageLink) { @@ -863,9 +834,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.getForObject(baseURL + "/api/dashboard/maxDatapointsLimit", Long.class); } - public Optional getDashboardInfoById(String dashboardId) { + public Optional getDashboardInfoById(DashboardId dashboardId) { try { - ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/api/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId); + ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/api/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId.getId()); return Optional.ofNullable(dashboardInfo.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -876,9 +847,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getDashboardById(String dashboardId) { + public Optional getDashboardById(DashboardId dashboardId) { try { - ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/api/dashboard/{dashboardId}", Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/api/dashboard/{dashboardId}", Dashboard.class, dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -893,13 +864,13 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); } - public void deleteDashboard(String dashboardId) { - restTemplate.delete(baseURL + "/api/dashboard/{dashboardId}", dashboardId); + public void deleteDashboard(DashboardId dashboardId) { + restTemplate.delete(baseURL + "/api/dashboard/{dashboardId}", dashboardId.getId()); } - public Optional assignDashboardToCustomer(String customerId, String dashboardId) { + public Optional assignDashboardToCustomer(CustomerId customerId, DashboardId dashboardId) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId.getId(), dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -910,9 +881,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional unassignDashboardFromCustomer(String customerId, String dashboardId) { + public Optional unassignDashboardFromCustomer(CustomerId customerId, DashboardId dashboardId) { try { - ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/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.getId(), dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -923,9 +894,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional updateDashboardCustomers(String dashboardId, List customerIds) { + public Optional updateDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers", customerIdArray, Dashboard.class, dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -936,9 +908,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional addDashboardCustomers(String dashboardId, List customerIds) { + public Optional addDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/add", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/add", customerIdArray, Dashboard.class, dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -949,9 +922,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional removeDashboardCustomers(String dashboardId, List customerIds) { + public Optional removeDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/remove", customerIds, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/remove", customerIdArray, Dashboard.class, dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -962,9 +936,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional assignDashboardToPublicCustomer(String dashboardId) { + public Optional assignDashboardToPublicCustomer(DashboardId dashboardId) { try { - ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId); + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -975,9 +949,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional unassignDashboardFromPublicCustomer(String dashboardId) { + public Optional unassignDashboardFromPublicCustomer(DashboardId dashboardId) { try { - ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/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.getId()); return Optional.ofNullable(dashboard.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -988,17 +962,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TextPageData getTenantDashboards(String tenantId, TextPageLink pageLink) { + public TextPageData getTenantDashboards(TenantId tenantId, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("tenantId", tenantId); + params.put("tenantId", tenantId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( baseURL + "/api/tenant/{tenantId}/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params - ).getBody(); + }, params).getBody(); } public TextPageData getTenantDashboards(TextPageLink pageLink) { @@ -1008,27 +980,23 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { baseURL + "/api/tenant/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params - ).getBody(); + }, params).getBody(); } - public TimePageData getCustomerDashboards(String customerId, TimePageLink pageLink) { + public TimePageData getCustomerDashboards(CustomerId customerId, TimePageLink pageLink) { Map params = new HashMap<>(); - params.put("customerId", customerId); + params.put("customerId", customerId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( baseURL + "/api/customer/{customerId}/dashboards?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params - ).getBody(); + }, params).getBody(); } - public Optional getDeviceById(String deviceId) { + public Optional getDeviceById(DeviceId deviceId) { try { - ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}", Device.class, deviceId); + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}", Device.class, deviceId.getId()); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1043,13 +1011,13 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); } - public void deleteDevice(String deviceId) { - restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId); + public void deleteDevice(DeviceId deviceId) { + restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId.getId()); } - public Optional assignDeviceToCustomer(String customerId, String deviceId) { + public Optional assignDeviceToCustomer(CustomerId customerId, DeviceId deviceId) { try { - ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId, deviceId); + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId.getId(), deviceId.getId()); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1060,9 +1028,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional unassignDeviceFromCustomer(String deviceId) { + public Optional unassignDeviceFromCustomer(DeviceId deviceId) { try { - ResponseEntity device = restTemplate.exchange(baseURL + "/api/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.getId()); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1073,9 +1041,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional assignDeviceToPublicCustomer(String deviceId) { + public Optional assignDeviceToPublicCustomer(DeviceId deviceId) { try { - ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/public/device/{deviceId}", null, Device.class, deviceId); + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/public/device/{deviceId}", null, Device.class, deviceId.getId()); return Optional.ofNullable(device.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1086,9 +1054,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getDeviceCredentialsByDeviceId(String deviceId) { + public Optional getDeviceCredentialsByDeviceId(DeviceId deviceId) { try { - ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}/credentials", DeviceCredentials.class, deviceId); + ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}/credentials", DeviceCredentials.class, deviceId.getId()); return Optional.ofNullable(deviceCredentials.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1111,9 +1079,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { baseURL + "/api/tenant/devices?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params) - .getBody(); + }, params).getBody(); } public Optional getTenantDevice(String deviceName) { @@ -1129,26 +1095,23 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TextPageData getCustomerDevices(String customerId, String type, TextPageLink pageLink) { + public TextPageData getCustomerDevices(CustomerId customerId, String deviceType, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("customerId", customerId); - params.put("type", type); + params.put("customerId", customerId.getId().toString()); + params.put("type", deviceType); addPageLinkToParam(params, pageLink); return restTemplate.exchange( baseURL + "/api/customer/{customerId}/devices?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params) - .getBody(); + }, params).getBody(); } - public List getDevicesByIds(List deviceIds) { + public List getDevicesByIds(List deviceIds) { return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - listToString(deviceIds)).getBody(); + }, listIdsToString(deviceIds)).getBody(); } public List findByQuery(DeviceSearchQuery query) { @@ -1175,8 +1138,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.POST, new HttpEntity<>(claimRequest), new ParameterizedTypeReference() { - }, - deviceName).getBody(); + }, deviceName).getBody(); } public void reClaimDevice(String deviceName) { @@ -1187,14 +1149,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/relation", null); } - public void deleteRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { + public void deleteRelation(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup, EntityId toId) { Map params = new HashMap<>(); - params.put("fromId", fromId); - params.put("fromType", fromType); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); params.put("relationType", relationType); - params.put("relationTypeGroup", relationTypeGroup); - params.put("toId", toId); - params.put("toType", toType); + params.put("relationTypeGroup", relationTypeGroup.name()); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); restTemplate.delete(baseURL + "/api/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", params); } @@ -1202,14 +1164,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/relations?entityId={entityId}&entityType={entityType}", entityId.getId().toString(), entityId.getEntityType().name()); } - public Optional getRelation(String fromId, String fromType, String relationType, String relationTypeGroup, String toId, String toType) { + public Optional getRelation(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup, EntityId toId) { Map params = new HashMap<>(); - params.put("fromId", fromId); - params.put("fromType", fromType); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); params.put("relationType", relationType); - params.put("relationTypeGroup", relationTypeGroup); - params.put("toId", toId); - params.put("toType", toType); + params.put("relationTypeGroup", relationTypeGroup.name()); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); try { ResponseEntity entityRelation = restTemplate.getForEntity( @@ -1226,11 +1188,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public List findByFrom(String fromId, String fromType, String relationTypeGroup) { + public List findByFrom(EntityId fromId, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("fromId", fromId); - params.put("fromType", fromType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", @@ -1241,11 +1203,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public List findInfoByFrom(String fromId, String fromType, String relationTypeGroup) { + public List findInfoByFrom(EntityId fromId, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("fromId", fromId); - params.put("fromType", fromType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations/info?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", @@ -1256,12 +1218,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public List findByFrom(String fromId, String fromType, String relationType, String relationTypeGroup) { + public List findByFrom(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("fromId", fromId); - params.put("fromType", fromType); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); params.put("relationType", relationType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", @@ -1272,11 +1234,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public List findByTo(String toId, String toType, String relationTypeGroup) { + public List findByTo(EntityId toId, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("toId", toId); - params.put("toType", toType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", @@ -1287,11 +1249,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public List findInfoByTo(String toId, String toType, String relationTypeGroup) { + public List findInfoByTo(EntityId toId, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("toId", toId); - params.put("toType", toType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", @@ -1302,12 +1264,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public List findByTo(String toId, String toType, String relationType, String relationTypeGroup) { + public List findByTo(EntityId toId, String relationType, RelationTypeGroup relationTypeGroup) { Map params = new HashMap<>(); - params.put("toId", toId); - params.put("toType", toType); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); params.put("relationType", relationType); - params.put("relationTypeGroup", relationTypeGroup); + params.put("relationTypeGroup", relationTypeGroup.name()); return restTemplate.exchange( baseURL + "/api/relations?toId={toId}&toType={toType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", @@ -1336,9 +1298,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public Optional getEntityViewById(String entityViewId) { + public Optional getEntityViewById(EntityViewId entityViewId) { try { - ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/{entityViewId}", EntityView.class, entityViewId); + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/{entityViewId}", EntityView.class, entityViewId.getId()); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1370,9 +1332,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional assignEntityViewToCustomer(String customerId, String entityViewId) { + public Optional assignEntityViewToCustomer(CustomerId customerId, EntityViewId entityViewId) { try { - ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId, entityViewId); + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId.getId(), entityViewId.getId()); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1383,13 +1345,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional unassignEntityViewFromCustomer(String entityViewId) { + public Optional unassignEntityViewFromCustomer(EntityViewId entityViewId) { try { - ResponseEntity entityView = restTemplate.exchange( - baseURL + "/api/customer/entityView/{entityViewId}", - HttpMethod.DELETE, - HttpEntity.EMPTY, - EntityView.class, entityViewId); + ResponseEntity entityView = restTemplate.exchange(baseURL + "/api/customer/entityView/{entityViewId}", HttpMethod.DELETE, HttpEntity.EMPTY, EntityView.class, entityViewId.getId()); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1400,31 +1358,29 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TextPageData getCustomerEntityViews(String customerId, String type, TextPageLink pageLink) { + public TextPageData getCustomerEntityViews(CustomerId customerId, String entityViewType, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("customerId", customerId); - params.put("type", type); + params.put("customerId", customerId.getId().toString()); + params.put("type", entityViewType); addPageLinkToParam(params, pageLink); return restTemplate.exchange( baseURL + "/api/customer/{customerId}/entityViews?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); } - public TextPageData getTenantEntityViews(String type, TextPageLink pageLink) { + public TextPageData getTenantEntityViews(String entityViewType, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("type", type); + params.put("type", entityViewType); addPageLinkToParam(params, pageLink); return restTemplate.exchange( baseURL + "/api/tenant/entityViews?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); } public List findByQuery(EntityViewSearchQuery query) { @@ -1437,9 +1393,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public Optional assignEntityViewToPublicCustomer(String entityViewId) { + public Optional assignEntityViewToPublicCustomer(EntityViewId entityViewId) { try { - ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId); + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId.getId()); return Optional.ofNullable(entityView.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1450,12 +1406,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public TimePageData getEvents(EntityId entityId, String eventType, String tenantId, TimePageLink pageLink) { + public TimePageData getEvents(EntityId entityId, String eventType, TenantId tenantId, TimePageLink pageLink) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); params.put("eventType", eventType); - params.put("tenantId", tenantId); + params.put("tenantId", tenantId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( @@ -1467,11 +1423,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public TimePageData getEvents(EntityId entityId, String tenantId, TimePageLink pageLink) { + public TimePageData getEvents(EntityId entityId, TenantId tenantId, TimePageLink pageLink) { Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); - params.put("tenantId", tenantId); + params.put("tenantId", tenantId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( @@ -1483,8 +1439,8 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params).getBody(); } - public void handleOneWayDeviceRPCRequest(String deviceId, JsonNode requestBody) { - restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId); + public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { + restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); } public JsonNode handleTwoWayDeviceRPCRequest(String deviceId, JsonNode requestBody) { @@ -1497,9 +1453,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { deviceId).getBody(); } - public Optional getRuleChainById(String ruleChainId) { + public Optional getRuleChainById(RuleChainId ruleChainId) { try { - ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId); + ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId.getId()); return Optional.ofNullable(ruleChain.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1510,9 +1466,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } - public Optional getRuleChainMetaData(String ruleChainId) { + public Optional getRuleChainMetaData(RuleChainId ruleChainId) { try { - ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId); + ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId.getId()); return Optional.ofNullable(ruleChainMetaData.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1527,9 +1483,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); } - public Optional setRootRuleChain(String ruleChainId) { + public Optional setRootRuleChain(RuleChainId ruleChainId) { try { - ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId); + ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId()); return Optional.ofNullable(ruleChain.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1548,21 +1504,21 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { Map params = new HashMap<>(); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/ruleChains" + getUrlParams(pageLink), + baseURL + "/api/ruleChains?" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - } - ).getBody(); + }, + params).getBody(); } - public void deleteRuleChain(String ruleChainId) { - restTemplate.delete(baseURL + "/api/ruleChain/{ruleChainId}", ruleChainId); + public void deleteRuleChain(RuleChainId ruleChainId) { + restTemplate.delete(baseURL + "/api/ruleChain/{ruleChainId}", ruleChainId.getId()); } - public Optional getLatestRuleNodeDebugInput(String ruleNodeId) { + public Optional getLatestRuleNodeDebugInput(RuleNodeId ruleNodeId) { try { - ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/api/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId); + ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/api/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId.getId()); return Optional.ofNullable(jsonNode.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1668,19 +1624,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } - public List getTimeseries(EntityId entityId, List keys, Long startTs, Long endTs, Long interval, Integer limit, String agg) { + public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, TimePageLink pageLink) { Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); params.put("keys", listToString(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); + params.put("agg", agg == null ? "NONE" : agg.name()); Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{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}&interval={interval}&agg={agg}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>>() { @@ -1807,9 +1761,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } - public Optional getTenantById(String tenantId) { + public Optional getTenantById(TenantId tenantId) { try { - ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/{tenantId}", Tenant.class, tenantId); + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/{tenantId}", Tenant.class, tenantId.getId()); return Optional.ofNullable(tenant.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1824,8 +1778,8 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); } - public void deleteTenant(String tenantId) { - restTemplate.delete(baseURL + "/api/tenant/{tenantId}", tenantId); + public void deleteTenant(TenantId tenantId) { + restTemplate.delete(baseURL + "/api/tenant/{tenantId}", tenantId.getId()); } public TextPageData getTenants(TextPageLink pageLink) { @@ -1836,13 +1790,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); } - public Optional getUserById(String userId) { + public Optional getUserById(UserId userId) { try { - ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId); + ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId()); return Optional.ofNullable(user.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1857,9 +1810,9 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.getForEntity(baseURL + "/api/user/tokenAccessEnabled", Boolean.class).getBody(); } - public Optional getUserToken(String userId) { + public Optional getUserToken(UserId userId) { try { - ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/api/user/{userId}/token", JsonNode.class, userId); + ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/api/user/{userId}/token", JsonNode.class, userId.getId()); return Optional.ofNullable(userToken.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1878,17 +1831,17 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/user/sendActivationMail?email={email}", null, email); } - public String getActivationLink(String userId) { - return restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId).getBody(); + public String getActivationLink(UserId userId) { + return restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId.getId()).getBody(); } - public void deleteUser(String userId) { - restTemplate.delete(baseURL + "/api/user/{userId}", userId); + public void deleteUser(UserId userId) { + restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId()); } - public TextPageData getTenantAdmins(String tenantId, TextPageLink pageLink) { + public TextPageData getTenantAdmins(TenantId tenantId, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("tenantId", tenantId); + params.put("tenantId", tenantId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( @@ -1896,13 +1849,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); } - public TextPageData getCustomerUsers(String customerId, TextPageLink pageLink) { + public TextPageData getCustomerUsers(CustomerId customerId, TextPageLink pageLink) { Map params = new HashMap<>(); - params.put("customerId", customerId); + params.put("customerId", customerId.getId().toString()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( @@ -1910,22 +1862,21 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }, - params).getBody(); + }, params).getBody(); } - public void setUserCredentialsEnabled(String userId, boolean userCredentialsEnabled) { + public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { restTemplate.postForLocation( baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", null, - userId, + userId.getId(), userCredentialsEnabled); } - public Optional getWidgetsBundleById(String widgetsBundleId) { + public Optional getWidgetsBundleById(WidgetsBundleId widgetsBundleId) { try { ResponseEntity widgetsBundle = - restTemplate.getForEntity(baseURL + "/api/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId); + restTemplate.getForEntity(baseURL + "/api/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId.getId()); return Optional.ofNullable(widgetsBundle.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1940,8 +1891,8 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/widgetsBundle", widgetsBundle, WidgetsBundle.class).getBody(); } - public void deleteWidgetsBundle(String widgetsBundleId) { - restTemplate.delete(baseURL + "/api/widgetsBundle/{widgetsBundleId}", widgetsBundleId); + public void deleteWidgetsBundle(WidgetsBundleId widgetsBundleId) { + restTemplate.delete(baseURL + "/api/widgetsBundle/{widgetsBundleId}", widgetsBundleId.getId()); } public TextPageData getWidgetsBundles(TextPageLink pageLink) { @@ -1952,7 +1903,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }).getBody(); + }, params).getBody(); } public List getWidgetsBundles() { @@ -1964,10 +1915,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public Optional getWidgetTypeById(String widgetTypeId) { + public Optional getWidgetTypeById(WidgetsBundleId widgetTypeId) { try { ResponseEntity widgetType = - restTemplate.getForEntity(baseURL + "/api/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId); + restTemplate.getForEntity(baseURL + "/api/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId.getId()); return Optional.ofNullable(widgetType.getBody()); } catch (HttpClientErrorException exception) { if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { @@ -1982,8 +1933,8 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/widgetType", widgetType, WidgetType.class).getBody(); } - public void deleteWidgetType(String widgetTypeId) { - restTemplate.delete(baseURL + "/api/widgetType/{widgetTypeId}", widgetTypeId); + public void deleteWidgetType(WidgetTypeId widgetTypeId) { + restTemplate.delete(baseURL + "/api/widgetType/{widgetTypeId}", widgetTypeId.getId()); } public List getBundleWidgetTypes(boolean isSystem, String bundleAlias) { @@ -2016,6 +1967,34 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + 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}"; + if (!isEmpty(pageLink.getTextSearch())) { + urlParams += "&textSearch={textSearch}"; + } + if (!isEmpty(pageLink.getIdOffset())) { + urlParams += "&idOffset={idOffset}"; + } + if (!isEmpty(pageLink.getTextOffset())) { + urlParams += "&textOffset={textOffset}"; + } + return urlParams; + } + private void addPageLinkToParam(Map params, TimePageLink pageLink) { params.put("limit", String.valueOf(pageLink.getLimit())); if (pageLink.getStartTime() != null) { @@ -2049,6 +2028,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return String.join(",", list); } + private String listIdsToString(List list) { + return listToString(list.stream().map(id -> id.getId().toString()).collect(Collectors.toList())); + } + + private String listEnumToString(List list) { + return listToString(list.stream().map(Enum::name).collect(Collectors.toList())); + } + @Override public void close() { if (service != null) { From e7dfe75e410207b2495a1502e3bf4e5939492a8f Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Wed, 19 Feb 2020 14:11:20 +0200 Subject: [PATCH 201/261] Feature/rest client (#2428) * refactored and improvement Rest Client * refactored Rest Client, made old methods deprecated --- .../thingsboard/client/tools/RestClient.java | 349 ++++++++++-------- 1 file changed, 185 insertions(+), 164 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 91d9e1bfec..1b5c4e5435 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -145,6 +145,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return response; } + public RestTemplate getRestTemplate() { + return restTemplate; + } + public String getToken() { return token; } @@ -174,170 +178,6 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.getInterceptors().add(this); } - public Optional findDevice(String name) { - Map params = new HashMap(); - params.put("deviceName", name); - try { - ResponseEntity deviceEntity = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, params); - return Optional.of(deviceEntity.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } - } - - public Optional findCustomer(String title) { - Map params = new HashMap(); - params.put("customerTitle", title); - try { - ResponseEntity customerEntity = restTemplate.getForEntity(baseURL + "/api/tenant/customers?customerTitle={customerTitle}", Customer.class, params); - return Optional.of(customerEntity.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } - } - - public Optional findAsset(String name) { - Map params = new HashMap(); - params.put("assetName", name); - try { - ResponseEntity assetEntity = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, params); - return Optional.of(assetEntity.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } - } - - public Optional getAttributes(String accessToken, String clientKeys, String sharedKeys) { - Map params = new HashMap<>(); - params.put("accessToken", accessToken); - params.put("clientKeys", clientKeys); - params.put("sharedKeys", sharedKeys); - try { - ResponseEntity telemetryEntity = restTemplate.getForEntity(baseURL + "/api/v1/{accessToken}/attributes?clientKeys={clientKeys}&sharedKeys={sharedKeys}", JsonNode.class, params); - return Optional.of(telemetryEntity.getBody()); - } catch (HttpClientErrorException exception) { - if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { - return Optional.empty(); - } else { - throw exception; - } - } - } - - public Customer createCustomer(Customer customer) { - return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); - } - - public Customer createCustomer(String title) { - Customer customer = new Customer(); - customer.setTitle(title); - return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); - } - - public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) { - DeviceCredentials deviceCredentials = getCredentials(deviceId); - deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); - deviceCredentials.setCredentialsId(token); - return saveDeviceCredentials(deviceCredentials); - } - - public Device createDevice(String name, String type) { - Device device = new Device(); - device.setName(name); - device.setType(type); - return doCreateDevice(device, null); - } - - public Device createDevice(Device device) { - return doCreateDevice(device, null); - } - - public Device createDevice(Device device, String accessToken) { - return doCreateDevice(device, accessToken); - } - - private Device doCreateDevice(Device device, String accessToken) { - Map params = new HashMap<>(); - String deviceCreationUrl = "/api/device"; - if (!StringUtils.isEmpty(accessToken)) { - deviceCreationUrl = deviceCreationUrl + "?accessToken={accessToken}"; - params.put("accessToken", accessToken); - } - return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody(); - } - - public Asset createAsset(Asset asset) { - return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); - } - - public Asset createAsset(String name, String type) { - Asset asset = new Asset(); - asset.setName(name); - asset.setType(type); - return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); - } - - public Alarm createAlarm(Alarm alarm) { - return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); - } - - public Device assignDevice(CustomerId customerId, DeviceId deviceId) { - return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, - customerId.toString(), deviceId.toString()).getBody(); - } - - public Asset assignAsset(CustomerId customerId, AssetId assetId) { - return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", HttpEntity.EMPTY, Asset.class, - customerId.toString(), assetId.toString()).getBody(); - } - - public EntityRelation makeRelation(String relationType, EntityId idFrom, EntityId idTo) { - EntityRelation relation = new EntityRelation(); - relation.setFrom(idFrom); - relation.setTo(idTo); - relation.setType(relationType); - 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 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(); - } - - public RestTemplate getRestTemplate() { - return restTemplate; - } - public Optional getAdminSettings(String key) { try { ResponseEntity adminSettings = restTemplate.getForEntity(baseURL + "/api/admin/settings/{key}", AdminSettings.class, key); @@ -467,6 +307,11 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + @Deprecated + public Alarm createAlarm(Alarm alarm) { + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); + } + public Optional getAssetById(AssetId assetId) { try { ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId.getId()); @@ -603,6 +448,41 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } + @Deprecated + public Optional findAsset(String name) { + Map params = new HashMap(); + params.put("assetName", name); + try { + ResponseEntity assetEntity = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, params); + return Optional.of(assetEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public Asset createAsset(Asset asset) { + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + @Deprecated + public Asset createAsset(String name, String type) { + Asset asset = new Asset(); + asset.setName(name); + asset.setType(type); + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + @Deprecated + public Asset assignAsset(CustomerId customerId, AssetId assetId) { + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", HttpEntity.EMPTY, Asset.class, + customerId.toString(), assetId.toString()).getBody(); + } + public TimePageData getAuditLogsByCustomerId(CustomerId customerId, TimePageLink pageLink, List actionTypes) { Map params = new HashMap<>(); params.put("customerId", customerId.getId().toString()); @@ -826,6 +706,34 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + @Deprecated + public Optional findCustomer(String title) { + Map params = new HashMap<>(); + params.put("customerTitle", title); + try { + ResponseEntity customerEntity = restTemplate.getForEntity(baseURL + "/api/tenant/customers?customerTitle={customerTitle}", Customer.class, params); + return Optional.of(customerEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public Customer createCustomer(Customer customer) { + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + + @Deprecated + public Customer createCustomer(String title) { + Customer customer = new Customer(); + customer.setTitle(title); + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + public Long getServerTime() { return restTemplate.getForObject(baseURL + "/api/dashboard/serverTime", Long.class); } @@ -994,6 +902,27 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + @Deprecated + public Dashboard createDashboard(Dashboard dashboard) { + return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); + } + + @Deprecated + 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 Optional getDeviceById(DeviceId deviceId) { try { ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}", Device.class, deviceId.getId()); @@ -1145,6 +1074,70 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); } + @Deprecated + public Device createDevice(String name, String type) { + Device device = new Device(); + device.setName(name); + device.setType(type); + return doCreateDevice(device, null); + } + + @Deprecated + public Device createDevice(Device device) { + return doCreateDevice(device, null); + } + + @Deprecated + public Device createDevice(Device device, String accessToken) { + return doCreateDevice(device, accessToken); + } + + @Deprecated + private Device doCreateDevice(Device device, String accessToken) { + Map params = new HashMap<>(); + String deviceCreationUrl = "/api/device"; + if (!StringUtils.isEmpty(accessToken)) { + deviceCreationUrl = deviceCreationUrl + "?accessToken={accessToken}"; + params.put("accessToken", accessToken); + } + return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody(); + } + + @Deprecated + public DeviceCredentials getCredentials(DeviceId id) { + return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); + } + + @Deprecated + public Optional findDevice(String name) { + Map params = new HashMap<>(); + params.put("deviceName", name); + try { + ResponseEntity deviceEntity = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, params); + return Optional.of(deviceEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) { + DeviceCredentials deviceCredentials = getCredentials(deviceId); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId(token); + return saveDeviceCredentials(deviceCredentials); + } + + @Deprecated + public Device assignDevice(CustomerId customerId, DeviceId deviceId) { + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, + customerId.toString(), deviceId.toString()).getBody(); + } + public void saveRelation(EntityRelation relation) { restTemplate.postForLocation(baseURL + "/api/relation", null); } @@ -1298,6 +1291,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } + @Deprecated + public EntityRelation makeRelation(String relationType, EntityId idFrom, EntityId idTo) { + EntityRelation relation = new EntityRelation(); + relation.setFrom(idFrom); + relation.setTo(idTo); + relation.setType(relationType); + return restTemplate.postForEntity(baseURL + "/api/relation", relation, EntityRelation.class).getBody(); + } + public Optional getEntityViewById(EntityViewId entityViewId) { try { ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/{entityViewId}", EntityView.class, entityViewId.getId()); @@ -1967,6 +1969,24 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } } + @Deprecated + public Optional getAttributes(String accessToken, String clientKeys, String sharedKeys) { + Map params = new HashMap<>(); + params.put("accessToken", accessToken); + params.put("clientKeys", clientKeys); + params.put("sharedKeys", sharedKeys); + try { + ResponseEntity telemetryEntity = restTemplate.getForEntity(baseURL + "/api/v1/{accessToken}/attributes?clientKeys={clientKeys}&sharedKeys={sharedKeys}", JsonNode.class, params); + return Optional.of(telemetryEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + private String getUrlParams(TimePageLink pageLink) { String urlParams = "limit={limit}&ascOrder={ascOrder}"; if (pageLink.getStartTime() != null) { @@ -2042,4 +2062,5 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { service.shutdown(); } } + } From 9d6b1c5d7ae576186d4aebfe497e8681ba280ed2 Mon Sep 17 00:00:00 2001 From: Mirco Pizzichini <52463156+mircopz@users.noreply.github.com> Date: Wed, 19 Feb 2020 13:47:56 +0100 Subject: [PATCH 202/261] Add option to set bar alignment in 'flot-bar-widget' (#2320) --- ui/src/app/widget/lib/flot-widget.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index d09b8e1dff..de3afcff33 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -399,7 +399,8 @@ export default class TbFlot { options.series.bars ={ show: true, lineWidth: 0, - fill: 0.9 + fill: 0.9, + align: settings.barAlignment || "left" } ctx.defaultBarWidth = settings.defaultBarWidth || 600; } @@ -975,6 +976,11 @@ export default class TbFlot { "type": "number", "default": 600 }; + properties["barAlignment"] = { + "title": "Bar alignment", + "type": "string", + "default": "left" + }; } properties["shadowSize"] = { "title": "Shadow size", @@ -1125,6 +1131,25 @@ export default class TbFlot { } if (chartType === 'bar') { schema["form"].push("defaultBarWidth"); + schema["form"].push({ + "key": "barAlignment", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "left", + "label": "Left" + }, + { + "value": "right", + "label": "Right" + }, + { + "value": "center", + "label": "Center" + } + ] + }); } schema["form"].push("shadowSize"); schema["form"].push({ From 07eaf4dd77c6c494d540c0b01424a6024d9b1406 Mon Sep 17 00:00:00 2001 From: zbcumt <57447148+zbcumt@users.noreply.github.com> Date: Wed, 19 Feb 2020 20:49:02 +0800 Subject: [PATCH 203/261] Update tencent-map.js (#2329) Solve the problem that the polygonOpacity attribute does not work in Tencent map. --- ui/src/app/widget/lib/tencent-map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/widget/lib/tencent-map.js b/ui/src/app/widget/lib/tencent-map.js index 68940bd313..ac6b4a8ea1 100644 --- a/ui/src/app/widget/lib/tencent-map.js +++ b/ui/src/app/widget/lib/tencent-map.js @@ -365,7 +365,7 @@ export default class TbTencentMap { map: this.map, path: latLangs, strokeColor: settings.polygonStrokeColor, - fillColor: settings.polygonColor, + fillColor: qq.maps.Color.fromHex(settings.polygonColor, settings.polygonOpacity), strokeWeight: settings.polygonStrokeWeight }); //initialize-tooltip @@ -410,7 +410,7 @@ export default class TbTencentMap { path: polygon.getPath(), map: this.map, strokeColor: color, - fillColor: color, + fillColor: qq.maps.Color.fromHex(color, settings.polygonOpacity), strokeWeight: settings.polygonStrokeWeight } polygon.setOptions(options); From 0fa963be85c657eb6b57c4dc5303cdf59e9c2e0a Mon Sep 17 00:00:00 2001 From: blackstar-baba <535650957@qq.com> Date: Fri, 10 Jan 2020 11:12:36 +0800 Subject: [PATCH 204/261] fix bug:Device reconnect abnormal when certificate authentication is turned on --- .../server/transport/mqtt/MqttTransportHandler.java | 4 ++-- .../transport/mqtt/MqttTransportServerInitializer.java | 6 +++--- 2 files changed, 5 insertions(+), 5 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 c2257c4807..84c7598d9b 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 @@ -100,12 +100,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private volatile DeviceSessionCtx deviceSessionCtx; private volatile GatewaySessionHandler gatewaySessionHandler; - MqttTransportHandler(MqttTransportContext context) { + MqttTransportHandler(MqttTransportContext context,SslHandler sslHandler) { this.sessionId = UUID.randomUUID(); this.context = context; this.transportService = context.getTransportService(); this.adaptor = context.getAdaptor(); - this.sslHandler = context.getSslHandler(); + this.sslHandler = sslHandler; this.mqttQoSMap = new ConcurrentHashMap<>(); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java index d0b65562df..306b8e953b 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java @@ -36,15 +36,15 @@ public class MqttTransportServerInitializer extends ChannelInitializer Date: Wed, 19 Feb 2020 15:03:45 +0200 Subject: [PATCH 205/261] Scroll timseries table (#2360) * fixed: md-table-container overflox-x changed to visible * fixed: md-table-container overflox-x changed to visible * fixed: md-table-container overflox-x changed to visible * fixed: md-tabs min-height is set to 0 * fixed: md-tabs min-height is set to 0 * fixed: 1) md-table-container overflox-x changed to visible; 2) md-tabs min-height is set to 0 * changed specificity for overriding --- ui/src/app/widget/lib/timeseries-table-widget.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/src/app/widget/lib/timeseries-table-widget.scss b/ui/src/app/widget/lib/timeseries-table-widget.scss index 47813dc710..4381ee7f1e 100644 --- a/ui/src/app/widget/lib/timeseries-table-widget.scss +++ b/ui/src/app/widget/lib/timeseries-table-widget.scss @@ -30,4 +30,12 @@ tb-timeseries-table-widget { .tb-data-table md-toolbar { z-index: 10; } + + md-table-container { + overflow-x: visible; + } + + md-tabs:not(.md-no-tab-content):not(.md-dynamic-height) { + min-height: 0; + } } From ec2c435db296ef17bd9daa56e4f4462fac5d3261 Mon Sep 17 00:00:00 2001 From: Dmitriy Mushat <54553744+Dmitriymush@users.noreply.github.com> Date: Wed, 19 Feb 2020 15:04:15 +0200 Subject: [PATCH 206/261] fixed: advanced setting add-on of default marker color cleared up (#2358) --- .../src/main/data/json/system/widget_bundles/maps.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/maps.json b/application/src/main/data/json/system/widget_bundles/maps.json index 7018349a66..82da974cba 100644 --- a/application/src/main/data/json/system/widget_bundles/maps.json +++ b/application/src/main/data/json/system/widget_bundles/maps.json @@ -128,10 +128,10 @@ "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 var $scope = self.ctx.$scope;\n $scope.self = self;\n }\n \n \n self.actionSources = function () {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n };\n", - "settingsSchema": "{\n \"schema\": {\n \"title\": \"Openstreet Map Configuration\",\n \"type\": \"object\",\n \"properties\": {\n \"mapProvider\": {\n \"title\": \"Map provider\",\n \"type\": \"string\",\n \"default\": \"OpenStreetMap.Mapnik\"\n },\n \"normalizationStep\": {\n \"title\": \"Normalization data step (ms)\",\n \"type\": \"number\",\n \"default\": 1000\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 \"polKeyName\": {\n \"title\": \"Polygon key name\",\n \"type\": \"string\",\n \"default\": \"coordinates\"\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"label\": {\n \"title\": \"Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useLabelFunction\": {\n \"title\": \"Use label function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"labelFunction\": {\n \"title\": \"Label function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"showTooltip\": {\n \"title\": \"Show tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"tooltipColor\": {\n \"title\": \"Tooltip background color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"tooltipFontColor\": {\n \"title\": \"Tooltip font color\",\n \"type\": \"string\",\n \"default\": \"#000\"\n },\n \"tooltipOpacity\": {\n \"title\": \"Tooltip opacity (0-1)\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"tooltipPattern\": {\n \"title\": \"Tooltip (for ex. 'Text ${keyName} units.' or Link text')\",\n \"type\": \"string\",\n \"default\": \"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}\"\n },\n \"useTooltipFunction\": {\n \"title\": \"Use tooltip function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"tooltipFunction\": {\n \"title\": \"Tooltip function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"color\": {\n \"title\": \"Path color\",\n \"type\": \"string\"\n },\n \"strokeWeight\": {\n \"title\": \"Stroke weight\",\n \"type\": \"number\",\n \"default\": 2\n },\n \"strokeOpacity\": {\n \"title\": \"Stroke opacity\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"useColorFunction\": {\n \"title\": \"Use path color function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"colorFunction\": {\n \"title\": \"Path color function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"usePolylineDecorator\": {\n \"title\": \"Use path decorator\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"decoratorSymbol\": {\n \"title\": \"Decorator symbol\",\n \"type\": \"string\",\n \"default\": \"arrowHead\"\n },\n \"decoratorSymbolSize\": {\n \"title\": \"Decorator symbol size (px)\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"useDecoratorCustomColor\": {\n \"title\": \"Use path decorator custom color\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"decoratorCustomColor\": {\n \"title\": \"Decorator custom color\",\n \"type\": \"string\",\n \"default\": \"#000\"\n },\n \"decoratorOffset\": {\n \"title\": \"Decorator offset\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"endDecoratorOffset\": {\n \"title\": \"End decorator offset\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"decoratorRepeat\": {\n \"title\": \"Decorator repeat\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"showPolygon\": {\n \"title\": \"Show polygon\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonTooltipPattern\": {\n \"title\": \"Tooltip (for ex. 'Text ${keyName} units.' or Link text')\",\n \"type\": \"string\",\n \"default\": \"${entityName}

TimeStamp: ${ts:7}\"\n },\n \"usePolygonTooltipFunction\": {\n \"title\": \"Use polygon tooltip function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonTooltipFunction\": {\n \"title\": \"Polygon tooltip function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"polygonColor\": {\n \"title\": \"Polygon color\",\n \"type\": \"string\"\n },\n \"polygonOpacity\": {\n \"title\": \"Polygon opacity\",\n \"type\": \"number\",\n \"default\": 0.5\n },\n \"polygonStrokeColor\": {\n \"title\": \"Polygon border color\",\n \"type\": \"string\"\n },\n \"polygonStrokeOpacity\": {\n \"title\": \"Polygon border opacity\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"polygonStrokeWeight\": {\n \"title\": \"Polygon border weight\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"usePolygonColorFunction\": {\n \"title\": \"Use polygon color function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonColorFunction\": {\n \"title\": \"Polygon Color function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"showPoints\": {\n \"title\": \"Show points\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"pointColor\": {\n \"title\": \"Point color\",\n \"type\": \"string\"\n },\n \"pointSize\": {\n \"title\": \"Point size (px)\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"usePointAsAnchor\": {\n \"title\": \"Use point as anchor\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"pointAsAnchorFunction\": {\n \"title\": \"Point as anchor function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"pointTooltipOnRightPanel\": {\n \"title\": \"Independant point tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"autocloseTooltip\": {\n \"title\": \"Auto-close point popup\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultMarkerColor\": {\n \"title\": \"color for default marker\",\n \"type\": \"string\"\n },\n \"markerImage\": {\n \"title\": \"Custom marker image\",\n \"type\": \"string\"\n },\n \"markerImageSize\": {\n \"title\": \"Custom marker image size (px)\",\n \"type\": \"number\",\n \"default\": 34\n },\n \"rotationAngle\": {\n \"title\": \"Set additional rotation angle for marker (deg)\",\n \"type\": \"number\",\n \"default\": 180\n },\n \"useMarkerImageFunction\":{\n \"title\":\"Use marker image function\",\n \"type\":\"boolean\",\n \"default\":false\n },\n \"markerImageFunction\":{\n \"title\":\"Marker image function: f(data, images, dsData, dsIndex)\",\n \"type\":\"string\"\n },\n \"markerImages\":{\n \"title\":\"Marker images\",\n \"type\":\"array\",\n \"items\":{\n \"title\":\"Marker image\",\n \"type\":\"string\"\n }\n }\n },\n \"required\": []\n },\n \"form\": [{\n \"key\": \"mapProvider\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"OpenStreetMap.Mapnik\",\n \"label\": \"OpenStreetMap.Mapnik (Default)\"\n }, {\n \"value\": \"OpenStreetMap.BlackAndWhite\",\n \"label\": \"OpenStreetMap.BlackAndWhite\"\n }, {\n \"value\": \"OpenStreetMap.HOT\",\n \"label\": \"OpenStreetMap.HOT\"\n }, {\n \"value\": \"Esri.WorldStreetMap\",\n \"label\": \"Esri.WorldStreetMap\"\n }, {\n \"value\": \"Esri.WorldTopoMap\",\n \"label\": \"Esri.WorldTopoMap\"\n }, {\n \"value\": \"CartoDB.Positron\",\n \"label\": \"CartoDB.Positron\"\n }, {\n \"value\": \"CartoDB.DarkMatter\",\n \"label\": \"CartoDB.DarkMatter\"\n }]\n }, \"normalizationStep\", \"latKeyName\", \"lngKeyName\", \"polKeyName\", \"showLabel\", \"label\", \"useLabelFunction\", {\n \"key\": \"labelFunction\",\n \"type\": \"javascript\"\n }, \"showTooltip\", {\n \"key\": \"tooltipColor\",\n \"type\": \"color\"\n }, {\n \"key\": \"tooltipFontColor\",\n \"type\": \"color\"\n },\"tooltipOpacity\", {\n \"key\": \"tooltipPattern\",\n \"type\": \"textarea\"\n }, \"useTooltipFunction\", {\n \"key\": \"tooltipFunction\",\n \"type\": \"javascript\"\n }, {\n \"key\": \"color\",\n \"type\": \"color\"\n }, \"useColorFunction\", {\n \"key\": \"colorFunction\",\n \"type\": \"javascript\"\n }, \"usePolylineDecorator\", {\n \"key\": \"decoratorSymbol\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"arrowHead\",\n \"label\": \"Arrow\"\n }, {\n \"value\": \"dash\",\n \"label\": \"Dash\"\n }]\n }, \"decoratorSymbolSize\", \"useDecoratorCustomColor\", {\n \"key\": \"decoratorCustomColor\",\n \"type\": \"color\"\n }, {\n \"key\": \"decoratorOffset\",\n \"type\": \"textarea\"\n },{\n \"key\": \"endDecoratorOffset\",\n \"type\": \"textarea\"\n }, {\n \"key\": \"decoratorRepeat\",\n \"type\": \"textarea\"\n }, \"strokeWeight\", \"strokeOpacity\", \"showPolygon\", {\n \"key\": \"polygonTooltipPattern\",\n \"type\": \"textarea\"\n },\"usePolygonTooltipFunction\", {\n \"key\": \"polygonTooltipFunction\",\n \"type\": \"javascript\"\n },{\n \"key\": \"polygonColor\",\n \"type\": \"color\"\n },\t\"polygonOpacity\", {\n \"key\": \"polygonStrokeColor\",\n \"type\": \"color\"\n },\t\"polygonStrokeOpacity\",\"polygonStrokeWeight\",\"usePolygonColorFunction\",\t{\n \"key\": \"polygonColorFunction\",\n \"type\": \"javascript\"\n },\"showPoints\",{\n \"key\": \"pointColor\",\n \"type\": \"color\"\n }, \"pointSize\",\"usePointAsAnchor\", {\n \"key\": \"pointAsAnchorFunction\",\n \"type\": \"javascript\"\n },\"pointTooltipOnRightPanel\", \"autocloseTooltip\", {\n \"key\": \"defaultMarkerColor\",\n \"type\": \"color\"\n }, {\n \"key\": \"markerImage\",\n \"type\": \"image\"\n }, \"markerImageSize\", \"rotationAngle\",\"useMarkerImageFunction\",\n {\n \"key\":\"markerImageFunction\",\n \"type\":\"javascript\"\n }, {\n \"key\":\"markerImages\",\n \"items\":[\n {\n \"key\":\"markerImages[]\",\n \"type\":\"image\"\n }\n ]\n }]\n}", + "settingsSchema": "{\n \"schema\": {\n \"title\": \"Openstreet Map Configuration\",\n \"type\": \"object\",\n \"properties\": {\n \"mapProvider\": {\n \"title\": \"Map provider\",\n \"type\": \"string\",\n \"default\": \"OpenStreetMap.Mapnik\"\n },\n \"normalizationStep\": {\n \"title\": \"Normalization data step (ms)\",\n \"type\": \"number\",\n \"default\": 1000\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 \"polKeyName\": {\n \"title\": \"Polygon key name\",\n \"type\": \"string\",\n \"default\": \"coordinates\"\n },\n \"showLabel\": {\n \"title\": \"Show label\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"label\": {\n \"title\": \"Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )\",\n \"type\": \"string\",\n \"default\": \"${entityName}\"\n },\n \"useLabelFunction\": {\n \"title\": \"Use label function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"labelFunction\": {\n \"title\": \"Label function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"showTooltip\": {\n \"title\": \"Show tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"tooltipColor\": {\n \"title\": \"Tooltip background color\",\n \"type\": \"string\",\n \"default\": \"#fff\"\n },\n \"tooltipFontColor\": {\n \"title\": \"Tooltip font color\",\n \"type\": \"string\",\n \"default\": \"#000\"\n },\n \"tooltipOpacity\": {\n \"title\": \"Tooltip opacity (0-1)\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"tooltipPattern\": {\n \"title\": \"Tooltip (for ex. 'Text ${keyName} units.' or Link text')\",\n \"type\": \"string\",\n \"default\": \"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}\"\n },\n \"useTooltipFunction\": {\n \"title\": \"Use tooltip function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"tooltipFunction\": {\n \"title\": \"Tooltip function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"color\": {\n \"title\": \"Path color\",\n \"type\": \"string\"\n },\n \"strokeWeight\": {\n \"title\": \"Stroke weight\",\n \"type\": \"number\",\n \"default\": 2\n },\n \"strokeOpacity\": {\n \"title\": \"Stroke opacity\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"useColorFunction\": {\n \"title\": \"Use path color function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"colorFunction\": {\n \"title\": \"Path color function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"usePolylineDecorator\": {\n \"title\": \"Use path decorator\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"decoratorSymbol\": {\n \"title\": \"Decorator symbol\",\n \"type\": \"string\",\n \"default\": \"arrowHead\"\n },\n \"decoratorSymbolSize\": {\n \"title\": \"Decorator symbol size (px)\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"useDecoratorCustomColor\": {\n \"title\": \"Use path decorator custom color\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"decoratorCustomColor\": {\n \"title\": \"Decorator custom color\",\n \"type\": \"string\",\n \"default\": \"#000\"\n },\n \"decoratorOffset\": {\n \"title\": \"Decorator offset\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"endDecoratorOffset\": {\n \"title\": \"End decorator offset\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"decoratorRepeat\": {\n \"title\": \"Decorator repeat\",\n \"type\": \"string\",\n \"default\": \"20px\"\n },\n \"showPolygon\": {\n \"title\": \"Show polygon\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonTooltipPattern\": {\n \"title\": \"Tooltip (for ex. 'Text ${keyName} units.' or Link text')\",\n \"type\": \"string\",\n \"default\": \"${entityName}

TimeStamp: ${ts:7}\"\n },\n \"usePolygonTooltipFunction\": {\n \"title\": \"Use polygon tooltip function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonTooltipFunction\": {\n \"title\": \"Polygon tooltip function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"polygonColor\": {\n \"title\": \"Polygon color\",\n \"type\": \"string\"\n },\n \"polygonOpacity\": {\n \"title\": \"Polygon opacity\",\n \"type\": \"number\",\n \"default\": 0.5\n },\n \"polygonStrokeColor\": {\n \"title\": \"Polygon border color\",\n \"type\": \"string\"\n },\n \"polygonStrokeOpacity\": {\n \"title\": \"Polygon border opacity\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"polygonStrokeWeight\": {\n \"title\": \"Polygon border weight\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"usePolygonColorFunction\": {\n \"title\": \"Use polygon color function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"polygonColorFunction\": {\n \"title\": \"Polygon Color function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"showPoints\": {\n \"title\": \"Show points\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"pointColor\": {\n \"title\": \"Point color\",\n \"type\": \"string\"\n },\n \"pointSize\": {\n \"title\": \"Point size (px)\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"usePointAsAnchor\": {\n \"title\": \"Use point as anchor\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"pointAsAnchorFunction\": {\n \"title\": \"Point as anchor function: f(data, dsData, dsIndex)\",\n \"type\": \"string\"\n },\n \"pointTooltipOnRightPanel\": {\n \"title\": \"Independant point tooltip\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"autocloseTooltip\": {\n \"title\": \"Auto-close point popup\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"markerImage\": {\n \"title\": \"Custom marker image\",\n \"type\": \"string\"\n },\n \"markerImageSize\": {\n \"title\": \"Custom marker image size (px)\",\n \"type\": \"number\",\n \"default\": 34\n },\n \"rotationAngle\": {\n \"title\": \"Set additional rotation angle for marker (deg)\",\n \"type\": \"number\",\n \"default\": 180\n },\n \"useMarkerImageFunction\":{\n \"title\":\"Use marker image function\",\n \"type\":\"boolean\",\n \"default\":false\n },\n \"markerImageFunction\":{\n \"title\":\"Marker image function: f(data, images, dsData, dsIndex)\",\n \"type\":\"string\"\n },\n \"markerImages\":{\n \"title\":\"Marker images\",\n \"type\":\"array\",\n \"items\":{\n \"title\":\"Marker image\",\n \"type\":\"string\"\n }\n }\n },\n \"required\": []\n },\n \"form\": [{\n \"key\": \"mapProvider\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"OpenStreetMap.Mapnik\",\n \"label\": \"OpenStreetMap.Mapnik (Default)\"\n }, {\n \"value\": \"OpenStreetMap.BlackAndWhite\",\n \"label\": \"OpenStreetMap.BlackAndWhite\"\n }, {\n \"value\": \"OpenStreetMap.HOT\",\n \"label\": \"OpenStreetMap.HOT\"\n }, {\n \"value\": \"Esri.WorldStreetMap\",\n \"label\": \"Esri.WorldStreetMap\"\n }, {\n \"value\": \"Esri.WorldTopoMap\",\n \"label\": \"Esri.WorldTopoMap\"\n }, {\n \"value\": \"CartoDB.Positron\",\n \"label\": \"CartoDB.Positron\"\n }, {\n \"value\": \"CartoDB.DarkMatter\",\n \"label\": \"CartoDB.DarkMatter\"\n }]\n }, \"normalizationStep\", \"latKeyName\", \"lngKeyName\", \"polKeyName\", \"showLabel\", \"label\", \"useLabelFunction\", {\n \"key\": \"labelFunction\",\n \"type\": \"javascript\"\n }, \"showTooltip\", {\n \"key\": \"tooltipColor\",\n \"type\": \"color\"\n }, {\n \"key\": \"tooltipFontColor\",\n \"type\": \"color\"\n },\"tooltipOpacity\", {\n \"key\": \"tooltipPattern\",\n \"type\": \"textarea\"\n }, \"useTooltipFunction\", {\n \"key\": \"tooltipFunction\",\n \"type\": \"javascript\"\n }, {\n \"key\": \"color\",\n \"type\": \"color\"\n }, \"useColorFunction\", {\n \"key\": \"colorFunction\",\n \"type\": \"javascript\"\n }, \"usePolylineDecorator\", {\n \"key\": \"decoratorSymbol\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [{\n \"value\": \"arrowHead\",\n \"label\": \"Arrow\"\n }, {\n \"value\": \"dash\",\n \"label\": \"Dash\"\n }]\n }, \"decoratorSymbolSize\", \"useDecoratorCustomColor\", {\n \"key\": \"decoratorCustomColor\",\n \"type\": \"color\"\n }, {\n \"key\": \"decoratorOffset\",\n \"type\": \"textarea\"\n },{\n \"key\": \"endDecoratorOffset\",\n \"type\": \"textarea\"\n }, {\n \"key\": \"decoratorRepeat\",\n \"type\": \"textarea\"\n }, \"strokeWeight\", \"strokeOpacity\", \"showPolygon\", {\n \"key\": \"polygonTooltipPattern\",\n \"type\": \"textarea\"\n },\"usePolygonTooltipFunction\", {\n \"key\": \"polygonTooltipFunction\",\n \"type\": \"javascript\"\n },{\n \"key\": \"polygonColor\",\n \"type\": \"color\"\n },\t\"polygonOpacity\", {\n \"key\": \"polygonStrokeColor\",\n \"type\": \"color\"\n },\t\"polygonStrokeOpacity\",\"polygonStrokeWeight\",\"usePolygonColorFunction\",\t{\n \"key\": \"polygonColorFunction\",\n \"type\": \"javascript\"\n },\"showPoints\",{\n \"key\": \"pointColor\",\n \"type\": \"color\"\n }, \"pointSize\",\"usePointAsAnchor\", {\n \"key\": \"pointAsAnchorFunction\",\n \"type\": \"javascript\"\n },\"pointTooltipOnRightPanel\", \"autocloseTooltip\", {\n \"key\": \"markerImage\",\n \"type\": \"image\"\n }, \"markerImageSize\", \"rotationAngle\",\"useMarkerImageFunction\",\n {\n \"key\":\"markerImageFunction\",\n \"type\":\"javascript\"\n }, {\n \"key\":\"markerImages\",\n \"items\":[\n {\n \"key\":\"markerImages[]\",\n \"type\":\"image\"\n }\n ]\n }]\n}", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n\\t37.771210000,-122.510960000,\\n\\t37.771340000,-122.510980000,\\n\\t37.771340000,-122.510980000,\\n\\t37.771360000,-122.510850000,\\n\\t37.771380000,-122.510550000,\\n\\t37.771400000,-122.509900000,\\n\\t37.771410000,-122.509660000,\\n\\t37.771430000,-122.509360000,\\n\\t37.771430000,-122.509270000,\\n\\t37.771450000,-122.508840000,\\n\\t37.771490000,-122.507880000,\\n\\t37.771490000,-122.507780000,\\n\\t37.771530000,-122.507140000,\\n\\t37.771550000,-122.506690000,\\n\\t37.771560000,-122.506310000,\\n\\t37.771600000,-122.505640000,\\n\\t37.771650000,-122.504540000,\\n\\t37.771670000,-122.503990000,\\n\\t37.771700000,-122.503490000,\\n\\t37.771740000,-122.502430000,\\n\\t37.771790000,-122.501360000,\\n\\t37.771840000,-122.500290000,\\n\\t37.771870000,-122.499730000,\\n\\t37.771890000,-122.499210000,\\n\\t37.771940000,-122.498140000,\\n\\t37.771990000,-122.497070000,\\n\\t37.772000000,-122.496690000,\\n\\t37.772020000,-122.496350000,\\n\\t37.772030000,-122.496110000,\\n\\t37.772040000,-122.496000000,\\n\\t37.772040000,-122.495890000,\\n\\t37.772060000,-122.495440000,\\n\\t37.772090000,-122.494930000,\\n\\t37.772120000,-122.494160000,\\n\\t37.772130000,-122.493860000,\\n\\t37.772180000,-122.492790000,\\n\\t37.772200000,-122.492300000,\\n\\t37.772220000,-122.491840000,\\n\\t37.772230000,-122.491710000,\\n\\t37.772280000,-122.490630000,\\n\\t37.772330000,-122.489560000,\\n\\t37.772330000,-122.489470000,\\n\\t37.772360000,-122.489030000,\\n\\t37.772380000,-122.488490000,\\n\\t37.772430000,-122.487420000,\\n\\t37.772450000,-122.486980000,\\n\\t37.772480000,-122.486360000,\\n\\t37.772520000,-122.485280000,\\n\\t37.772560000,-122.484400000,\\n\\t37.772570000,-122.484300000,\\n\\t37.772570000,-122.484150000,\\n\\t37.772620000,-122.483140000,\\n\\t37.772680000,-122.482050000,\\n\\t37.772700000,-122.481370000,\\n\\t37.772710000,-122.481000000,\\n\\t37.772730000,-122.480740000,\\n\\t37.772770000,-122.479930000,\\n\\t37.772820000,-122.478860000,\\n\\t37.772870000,-122.477790000,\\n\\t37.772900000,-122.477110000,\\n\\t37.772920000,-122.476710000,\\n\\t37.772960000,-122.475650000,\\n\\t37.772990000,-122.474950000,\\n\\t37.773010000,-122.474580000,\\n\\t37.773060000,-122.473450000,\\n\\t37.773120000,-122.472330000,\\n\\t37.773140000,-122.471850000,\\n\\t37.773140000,-122.471730000,\\n\\t37.773150000,-122.471640000,\\n\\t37.773170000,-122.471260000,\\n\\t37.773190000,-122.470570000,\\n\\t37.773210000,-122.470190000,\\n\\t37.773230000,-122.469770000,\\n\\t37.773250000,-122.469370000,\\n\\t37.773260000,-122.469120000,\\n\\t37.773290000,-122.468490000,\\n\\t37.773300000,-122.468150000,\\n\\t37.773310000,-122.468050000,\\n\\t37.773310000,-122.467940000,\\n\\t37.773320000,-122.467740000,\\n\\t37.773350000,-122.467270000,\\n\\t37.773360000,-122.466980000,\\n\\t37.773360000,-122.466870000,\\n\\t37.773370000,-122.466610000,\\n\\t37.773390000,-122.466300000,\\n\\t37.773400000,-122.466000000,\\n\\t37.773400000,-122.465910000,\\n\\t37.773410000,-122.465790000,\\n\\t37.773430000,-122.465520000,\\n\\t37.773460000,-122.465210000,\\n\\t37.773490000,-122.464980000,\\n\\t37.773500000,-122.464910000,\\n\\t37.773460000,-122.464830000,\\n\\t37.773560000,-122.464070000,\\n\\t37.773580000,-122.463900000,\\n\\t37.773590000,-122.463810000,\\n\\t37.773600000,-122.463780000,\\n\\t37.773610000,-122.463670000,\\n\\t37.773660000,-122.463320000,\\n\\t37.773740000,-122.462700000,\\n\\t37.773770000,-122.462440000,\\n\\t37.773860000,-122.461730000,\\n\\t37.773870000,-122.461640000,\\n\\t37.773920000,-122.461260000,\\n\\t37.773970000,-122.460890000,\\n\\t37.774010000,-122.460570000,\\n\\t37.774110000,-122.459760000,\\n\\t37.774140000,-122.459490000,\\n\\t37.774270000,-122.458520000,\\n\\t37.774270000,-122.458440000,\\n\\t37.774270000,-122.458380000,\\n\\t37.774320000,-122.458270000,\\n\\t37.774340000,-122.458050000,\\n\\t37.774510000,-122.456680000,\\n\\t37.774560000,-122.456310000,\\n\\t37.774700000,-122.455280000,\\n\\t37.774760000,-122.454780000,\\n\\t37.774770000,-122.454670000,\\n\\t37.774770000,-122.454670000,\\n\\t37.774670000,-122.454650000,\\n\\t37.774670000,-122.454650000,\\n\\t37.774580000,-122.454640000,\\n\\t37.774300000,-122.454580000,\\n\\t37.774190000,-122.454560000,\\n\\t37.773700000,-122.454460000,\\n\\t37.772910000,-122.454310000,\\n\\t37.772620000,-122.454260000,\\n\\t37.772430000,-122.454220000,\\n\\t37.771980000,-122.454110000,\\n\\t37.771910000,-122.454100000,\\n\\t37.771760000,-122.454060000,\\n\\t37.771690000,-122.454050000,\\n\\t37.771620000,-122.454030000,\\n\\t37.771530000,-122.454010000,\\n\\t37.771380000,-122.453970000,\\n\\t37.771250000,-122.453950000,\\n\\t37.771100000,-122.453930000,\\n\\t37.771020000,-122.453920000,\\n\\t37.770920000,-122.453900000,\\n\\t37.770810000,-122.453890000,\\n\\t37.770660000,-122.453860000,\\n\\t37.770110000,-122.453750000,\\n\\t37.769560000,-122.453640000,\\n\\t37.769360000,-122.453600000,\\n\\t37.769250000,-122.453580000,\\n\\t37.769180000,-122.453560000,\\n\\t37.769090000,-122.453540000,\\n\\t37.768780000,-122.453480000,\\n\\t37.768250000,-122.453380000,\\n\\t37.768160000,-122.453360000,\\n\\t37.767820000,-122.453290000,\\n\\t37.767310000,-122.453190000,\\n\\t37.767160000,-122.453160000,\\n\\t37.767010000,-122.453130000,\\n\\t37.766760000,-122.453070000,\\n\\t37.766550000,-122.453030000,\\n\\t37.766550000,-122.453030000,\\n\\t37.766390000,-122.452990000,\\n\\t37.766390000,-122.452990000,\\n\\t37.766290000,-122.453720000,\\n\\t37.766180000,-122.454610000,\\n\\t37.766130000,-122.454980000,\\n\\t37.765960000,-122.456290000,\\n\\t37.765960000,-122.456340000,\\n\\t37.765960000,-122.456360000,\\n\\t37.765960000,-122.456380000,\\n\\t37.765960000,-122.456410000,\\n\\t37.765960000,-122.456460000,\\n\\t37.765940000,-122.456630000,\\n\\t37.765930000,-122.456700000,\\n\\t37.765920000,-122.456810000,\\n\\t37.765910000,-122.456930000,\\n\\t37.765910000,-122.457020000,\\n\\t37.765920000,-122.457160000,\\n\\t37.765930000,-122.457270000,\\n\\t37.765940000,-122.457360000,\\n\\t37.765950000,-122.457410000,\\n\\t37.765960000,-122.457470000,\\n\\t37.765980000,-122.457560000,\\n\\t37.766010000,-122.457660000,\\n\\t37.766070000,-122.457830000,\\n\\t37.766070000,-122.457830000,\\n\\t37.766120000,-122.457980000,\\n\\t37.766180000,-122.458180000,\\n\\t37.766190000,-122.458200000,\\n\\t37.766240000,-122.458400000,\\n\\t37.766270000,-122.458530000,\\n\\t37.766290000,-122.458600000,\\n\\t37.766300000,-122.458690000,\\n\\t37.766300000,-122.458880000,\\n\\t37.766300000,-122.458970000,\\n\\t37.766280000,-122.459470000,\\n\\t37.766270000,-122.459520000,\\n\\t37.766270000,-122.459560000,\\n\\t37.766280000,-122.459600000,\\n\\t37.766280000,-122.459630000,\\n\\t37.766290000,-122.459670000,\\n\\t37.766300000,-122.459700000,\\n\\t37.766310000,-122.459730000,\\n\\t37.766320000,-122.459750000,\\n\\t37.766330000,-122.459770000,\\n\\t37.766350000,-122.459800000,\\n\\t37.766390000,-122.459860000,\\n\\t37.766340000,-122.459970000,\\n\\t37.766290000,-122.460150000,\\n\\t37.766290000,-122.460230000,\\n\\t37.766280000,-122.460280000,\\n\\t37.766260000,-122.460330000,\\n\\t37.766250000,-122.460420000,\\n\\t37.766240000,-122.460520000,\\n\\t37.766230000,-122.460670000,\\n\\t37.766230000,-122.460800000,\\n\\t37.766230000,-122.460900000,\\n\\t37.766210000,-122.461110000,\\n\\t37.766170000,-122.462030000,\\n\\t37.766160000,-122.462170000,\\n\\t37.766150000,-122.462520000,\\n\\t37.766120000,-122.463260000,\\n\\t37.766090000,-122.464130000,\\n\\t37.766070000,-122.464350000,\\n\\t37.766070000,-122.464440000,\\n\\t37.766060000,-122.464620000,\\n\\t37.766030000,-122.465400000,\\n\\t37.765980000,-122.466470000,\\n\\t37.765940000,-122.467530000,\\n\\t37.765930000,-122.467680000,\\n\\t37.765890000,-122.468600000,\\n\\t37.765860000,-122.468980000,\\n\\t37.765830000,-122.469660000,\\n\\t37.765780000,-122.470740000,\\n\\t37.765770000,-122.471030000,\\n\\t37.765770000,-122.471140000,\\n\\t37.765760000,-122.471380000,\\n\\t37.765740000,-122.471820000,\\n\\t37.765690000,-122.472950000,\\n\\t37.765680000,-122.473220000,\\n\\t37.765670000,-122.473320000,\\n\\t37.765640000,-122.474070000,\\n\\t37.765590000,-122.475140000,\\n\\t37.765590000,-122.475400000,\\n\\t37.765580000,-122.475520000,\\n\\t37.765550000,-122.476200000,\\n\\t37.765500000,-122.477180000,\\n\\t37.765500000,-122.477280000,\\n\\t37.765490000,-122.477400000,\\n\\t37.765490000,-122.477440000,\\n\\t37.765490000,-122.477440000,\\n\\t37.765490000,-122.477490000,\\n\\t37.765470000,-122.477770000,\\n\\t37.765450000,-122.478340000,\\n\\t37.765450000,-122.478420000,\\n\\t37.765430000,-122.478870000,\\n\\t37.765400000,-122.479430000,\\n\\t37.765380000,-122.479810000,\\n\\t37.765350000,-122.480480000,\\n\\t37.765300000,-122.481570000,\\n\\t37.765300000,-122.481660000,\\n\\t37.765290000,-122.481950000,\\n\\t37.765260000,-122.482640000,\\n\\t37.765220000,-122.483570000,\\n\\t37.765210000,-122.483710000,\\n\\t37.765210000,-122.483810000,\\n\\t37.765190000,-122.484130000,\\n\\t37.765160000,-122.484780000,\\n\\t37.765120000,-122.485860000,\\n\\t37.765110000,-122.485960000,\\n\\t37.765100000,-122.486170000,\\n\\t37.765070000,-122.486930000,\\n\\t37.765020000,-122.488000000,\\n\\t37.765020000,-122.488100000,\\n\\t37.765000000,-122.488360000,\\n\\t37.764980000,-122.488970000,\\n\\t37.764970000,-122.489070000,\\n\\t37.764930000,-122.490150000,\\n\\t37.764920000,-122.490240000,\\n\\t37.764910000,-122.490490000,\\n\\t37.764880000,-122.491210000,\\n\\t37.764830000,-122.492290000,\\n\\t37.764790000,-122.493260000,\\n\\t37.764780000,-122.493350000,\\n\\t37.764740000,-122.494420000,\\n\\t37.764720000,-122.494730000,\\n\\t37.764710000,-122.495500000,\\n\\t37.764700000,-122.495630000,\\n\\t37.764690000,-122.495940000,\\n\\t37.764680000,-122.496260000,\\n\\t37.764670000,-122.496480000,\\n\\t37.764600000,-122.497490000,\\n\\t37.764590000,-122.497650000,\\n\\t37.764550000,-122.498720000,\\n\\t37.764500000,-122.499780000,\\n\\t37.764450000,-122.500870000,\\n\\t37.764440000,-122.501060000,\\n\\t37.764400000,-122.501930000,\\n\\t37.764360000,-122.502990000,\\n\\t37.764310000,-122.504080000,\\n\\t37.764260000,-122.505140000,\\n\\t37.764260000,-122.505250000,\\n\\t37.764220000,-122.506220000,\\n\\t37.764170000,-122.507280000,\\n\\t37.764120000,-122.508360000,\\n\\t37.764100000,-122.508850000,\\n\\t37.764100000,-122.509150000,\\n\\t37.764100000,-122.509440000,\\n\\t37.764090000,-122.509760000,\\n\\t37.764080000,-122.510200000,\\n\\t37.764070000,-122.510320000,\\n\\t37.764060000,-122.510430000,\\n\\t37.764030000,-122.510430000,\\n\\t37.764030000,-122.510430000,\\n\\t37.763960000,-122.510430000,\\n\\t37.763960000,-122.510290000,\\n\\t37.764070000,-122.510320000,\\n\\t37.764170000,-122.510320000,\\n\\t37.764410000,-122.510340000,\\n\\t37.764610000,-122.510350000,\\n\\t37.764780000,-122.510330000,\\n\\t37.764960000,-122.510310000,\\n\\t37.765340000,-122.510280000,\\n\\t37.765570000,-122.510260000,\\n\\t37.765840000,-122.510250000,\\n\\t37.765900000,-122.510250000,\\n\\t37.766140000,-122.510260000,\\n\\t37.766410000,-122.510260000,\\n\\t37.766620000,-122.510270000,\\n\\t37.767040000,-122.510310000,\\n\\t37.767270000,-122.510330000,\\n\\t37.767620000,-122.510390000,\\n\\t37.767670000,-122.510390000,\\n\\t37.767740000,-122.510410000,\\n\\t37.767840000,-122.510430000,\\n\\t37.768280000,-122.510530000,\\n\\t37.768620000,-122.510600000,\\n\\t37.768690000,-122.510620000,\\n\\t37.769020000,-122.510690000,\\n\\t37.769260000,-122.510730000,\\n\\t37.769420000,-122.510750000,\\n\\t37.770010000,-122.510830000,\\n\\t37.770340000,-122.510860000,\\n\\t37.770460000,-122.510870000,\\n\\t37.770930000,-122.510930000,\\n\\t37.770980000,-122.510930000,\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : value + 2)];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n\\t37.771210000,-122.510960000,\\n\\t37.771340000,-122.510980000,\\n\\t37.771340000,-122.510980000,\\n\\t37.771360000,-122.510850000,\\n\\t37.771380000,-122.510550000,\\n\\t37.771400000,-122.509900000,\\n\\t37.771410000,-122.509660000,\\n\\t37.771430000,-122.509360000,\\n\\t37.771430000,-122.509270000,\\n\\t37.771450000,-122.508840000,\\n\\t37.771490000,-122.507880000,\\n\\t37.771490000,-122.507780000,\\n\\t37.771530000,-122.507140000,\\n\\t37.771550000,-122.506690000,\\n\\t37.771560000,-122.506310000,\\n\\t37.771600000,-122.505640000,\\n\\t37.771650000,-122.504540000,\\n\\t37.771670000,-122.503990000,\\n\\t37.771700000,-122.503490000,\\n\\t37.771740000,-122.502430000,\\n\\t37.771790000,-122.501360000,\\n\\t37.771840000,-122.500290000,\\n\\t37.771870000,-122.499730000,\\n\\t37.771890000,-122.499210000,\\n\\t37.771940000,-122.498140000,\\n\\t37.771990000,-122.497070000,\\n\\t37.772000000,-122.496690000,\\n\\t37.772020000,-122.496350000,\\n\\t37.772030000,-122.496110000,\\n\\t37.772040000,-122.496000000,\\n\\t37.772040000,-122.495890000,\\n\\t37.772060000,-122.495440000,\\n\\t37.772090000,-122.494930000,\\n\\t37.772120000,-122.494160000,\\n\\t37.772130000,-122.493860000,\\n\\t37.772180000,-122.492790000,\\n\\t37.772200000,-122.492300000,\\n\\t37.772220000,-122.491840000,\\n\\t37.772230000,-122.491710000,\\n\\t37.772280000,-122.490630000,\\n\\t37.772330000,-122.489560000,\\n\\t37.772330000,-122.489470000,\\n\\t37.772360000,-122.489030000,\\n\\t37.772380000,-122.488490000,\\n\\t37.772430000,-122.487420000,\\n\\t37.772450000,-122.486980000,\\n\\t37.772480000,-122.486360000,\\n\\t37.772520000,-122.485280000,\\n\\t37.772560000,-122.484400000,\\n\\t37.772570000,-122.484300000,\\n\\t37.772570000,-122.484150000,\\n\\t37.772620000,-122.483140000,\\n\\t37.772680000,-122.482050000,\\n\\t37.772700000,-122.481370000,\\n\\t37.772710000,-122.481000000,\\n\\t37.772730000,-122.480740000,\\n\\t37.772770000,-122.479930000,\\n\\t37.772820000,-122.478860000,\\n\\t37.772870000,-122.477790000,\\n\\t37.772900000,-122.477110000,\\n\\t37.772920000,-122.476710000,\\n\\t37.772960000,-122.475650000,\\n\\t37.772990000,-122.474950000,\\n\\t37.773010000,-122.474580000,\\n\\t37.773060000,-122.473450000,\\n\\t37.773120000,-122.472330000,\\n\\t37.773140000,-122.471850000,\\n\\t37.773140000,-122.471730000,\\n\\t37.773150000,-122.471640000,\\n\\t37.773170000,-122.471260000,\\n\\t37.773190000,-122.470570000,\\n\\t37.773210000,-122.470190000,\\n\\t37.773230000,-122.469770000,\\n\\t37.773250000,-122.469370000,\\n\\t37.773260000,-122.469120000,\\n\\t37.773290000,-122.468490000,\\n\\t37.773300000,-122.468150000,\\n\\t37.773310000,-122.468050000,\\n\\t37.773310000,-122.467940000,\\n\\t37.773320000,-122.467740000,\\n\\t37.773350000,-122.467270000,\\n\\t37.773360000,-122.466980000,\\n\\t37.773360000,-122.466870000,\\n\\t37.773370000,-122.466610000,\\n\\t37.773390000,-122.466300000,\\n\\t37.773400000,-122.466000000,\\n\\t37.773400000,-122.465910000,\\n\\t37.773410000,-122.465790000,\\n\\t37.773430000,-122.465520000,\\n\\t37.773460000,-122.465210000,\\n\\t37.773490000,-122.464980000,\\n\\t37.773500000,-122.464910000,\\n\\t37.773460000,-122.464830000,\\n\\t37.773560000,-122.464070000,\\n\\t37.773580000,-122.463900000,\\n\\t37.773590000,-122.463810000,\\n\\t37.773600000,-122.463780000,\\n\\t37.773610000,-122.463670000,\\n\\t37.773660000,-122.463320000,\\n\\t37.773740000,-122.462700000,\\n\\t37.773770000,-122.462440000,\\n\\t37.773860000,-122.461730000,\\n\\t37.773870000,-122.461640000,\\n\\t37.773920000,-122.461260000,\\n\\t37.773970000,-122.460890000,\\n\\t37.774010000,-122.460570000,\\n\\t37.774110000,-122.459760000,\\n\\t37.774140000,-122.459490000,\\n\\t37.774270000,-122.458520000,\\n\\t37.774270000,-122.458440000,\\n\\t37.774270000,-122.458380000,\\n\\t37.774320000,-122.458270000,\\n\\t37.774340000,-122.458050000,\\n\\t37.774510000,-122.456680000,\\n\\t37.774560000,-122.456310000,\\n\\t37.774700000,-122.455280000,\\n\\t37.774760000,-122.454780000,\\n\\t37.774770000,-122.454670000,\\n\\t37.774770000,-122.454670000,\\n\\t37.774670000,-122.454650000,\\n\\t37.774670000,-122.454650000,\\n\\t37.774580000,-122.454640000,\\n\\t37.774300000,-122.454580000,\\n\\t37.774190000,-122.454560000,\\n\\t37.773700000,-122.454460000,\\n\\t37.772910000,-122.454310000,\\n\\t37.772620000,-122.454260000,\\n\\t37.772430000,-122.454220000,\\n\\t37.771980000,-122.454110000,\\n\\t37.771910000,-122.454100000,\\n\\t37.771760000,-122.454060000,\\n\\t37.771690000,-122.454050000,\\n\\t37.771620000,-122.454030000,\\n\\t37.771530000,-122.454010000,\\n\\t37.771380000,-122.453970000,\\n\\t37.771250000,-122.453950000,\\n\\t37.771100000,-122.453930000,\\n\\t37.771020000,-122.453920000,\\n\\t37.770920000,-122.453900000,\\n\\t37.770810000,-122.453890000,\\n\\t37.770660000,-122.453860000,\\n\\t37.770110000,-122.453750000,\\n\\t37.769560000,-122.453640000,\\n\\t37.769360000,-122.453600000,\\n\\t37.769250000,-122.453580000,\\n\\t37.769180000,-122.453560000,\\n\\t37.769090000,-122.453540000,\\n\\t37.768780000,-122.453480000,\\n\\t37.768250000,-122.453380000,\\n\\t37.768160000,-122.453360000,\\n\\t37.767820000,-122.453290000,\\n\\t37.767310000,-122.453190000,\\n\\t37.767160000,-122.453160000,\\n\\t37.767010000,-122.453130000,\\n\\t37.766760000,-122.453070000,\\n\\t37.766550000,-122.453030000,\\n\\t37.766550000,-122.453030000,\\n\\t37.766390000,-122.452990000,\\n\\t37.766390000,-122.452990000,\\n\\t37.766290000,-122.453720000,\\n\\t37.766180000,-122.454610000,\\n\\t37.766130000,-122.454980000,\\n\\t37.765960000,-122.456290000,\\n\\t37.765960000,-122.456340000,\\n\\t37.765960000,-122.456360000,\\n\\t37.765960000,-122.456380000,\\n\\t37.765960000,-122.456410000,\\n\\t37.765960000,-122.456460000,\\n\\t37.765940000,-122.456630000,\\n\\t37.765930000,-122.456700000,\\n\\t37.765920000,-122.456810000,\\n\\t37.765910000,-122.456930000,\\n\\t37.765910000,-122.457020000,\\n\\t37.765920000,-122.457160000,\\n\\t37.765930000,-122.457270000,\\n\\t37.765940000,-122.457360000,\\n\\t37.765950000,-122.457410000,\\n\\t37.765960000,-122.457470000,\\n\\t37.765980000,-122.457560000,\\n\\t37.766010000,-122.457660000,\\n\\t37.766070000,-122.457830000,\\n\\t37.766070000,-122.457830000,\\n\\t37.766120000,-122.457980000,\\n\\t37.766180000,-122.458180000,\\n\\t37.766190000,-122.458200000,\\n\\t37.766240000,-122.458400000,\\n\\t37.766270000,-122.458530000,\\n\\t37.766290000,-122.458600000,\\n\\t37.766300000,-122.458690000,\\n\\t37.766300000,-122.458880000,\\n\\t37.766300000,-122.458970000,\\n\\t37.766280000,-122.459470000,\\n\\t37.766270000,-122.459520000,\\n\\t37.766270000,-122.459560000,\\n\\t37.766280000,-122.459600000,\\n\\t37.766280000,-122.459630000,\\n\\t37.766290000,-122.459670000,\\n\\t37.766300000,-122.459700000,\\n\\t37.766310000,-122.459730000,\\n\\t37.766320000,-122.459750000,\\n\\t37.766330000,-122.459770000,\\n\\t37.766350000,-122.459800000,\\n\\t37.766390000,-122.459860000,\\n\\t37.766340000,-122.459970000,\\n\\t37.766290000,-122.460150000,\\n\\t37.766290000,-122.460230000,\\n\\t37.766280000,-122.460280000,\\n\\t37.766260000,-122.460330000,\\n\\t37.766250000,-122.460420000,\\n\\t37.766240000,-122.460520000,\\n\\t37.766230000,-122.460670000,\\n\\t37.766230000,-122.460800000,\\n\\t37.766230000,-122.460900000,\\n\\t37.766210000,-122.461110000,\\n\\t37.766170000,-122.462030000,\\n\\t37.766160000,-122.462170000,\\n\\t37.766150000,-122.462520000,\\n\\t37.766120000,-122.463260000,\\n\\t37.766090000,-122.464130000,\\n\\t37.766070000,-122.464350000,\\n\\t37.766070000,-122.464440000,\\n\\t37.766060000,-122.464620000,\\n\\t37.766030000,-122.465400000,\\n\\t37.765980000,-122.466470000,\\n\\t37.765940000,-122.467530000,\\n\\t37.765930000,-122.467680000,\\n\\t37.765890000,-122.468600000,\\n\\t37.765860000,-122.468980000,\\n\\t37.765830000,-122.469660000,\\n\\t37.765780000,-122.470740000,\\n\\t37.765770000,-122.471030000,\\n\\t37.765770000,-122.471140000,\\n\\t37.765760000,-122.471380000,\\n\\t37.765740000,-122.471820000,\\n\\t37.765690000,-122.472950000,\\n\\t37.765680000,-122.473220000,\\n\\t37.765670000,-122.473320000,\\n\\t37.765640000,-122.474070000,\\n\\t37.765590000,-122.475140000,\\n\\t37.765590000,-122.475400000,\\n\\t37.765580000,-122.475520000,\\n\\t37.765550000,-122.476200000,\\n\\t37.765500000,-122.477180000,\\n\\t37.765500000,-122.477280000,\\n\\t37.765490000,-122.477400000,\\n\\t37.765490000,-122.477440000,\\n\\t37.765490000,-122.477440000,\\n\\t37.765490000,-122.477490000,\\n\\t37.765470000,-122.477770000,\\n\\t37.765450000,-122.478340000,\\n\\t37.765450000,-122.478420000,\\n\\t37.765430000,-122.478870000,\\n\\t37.765400000,-122.479430000,\\n\\t37.765380000,-122.479810000,\\n\\t37.765350000,-122.480480000,\\n\\t37.765300000,-122.481570000,\\n\\t37.765300000,-122.481660000,\\n\\t37.765290000,-122.481950000,\\n\\t37.765260000,-122.482640000,\\n\\t37.765220000,-122.483570000,\\n\\t37.765210000,-122.483710000,\\n\\t37.765210000,-122.483810000,\\n\\t37.765190000,-122.484130000,\\n\\t37.765160000,-122.484780000,\\n\\t37.765120000,-122.485860000,\\n\\t37.765110000,-122.485960000,\\n\\t37.765100000,-122.486170000,\\n\\t37.765070000,-122.486930000,\\n\\t37.765020000,-122.488000000,\\n\\t37.765020000,-122.488100000,\\n\\t37.765000000,-122.488360000,\\n\\t37.764980000,-122.488970000,\\n\\t37.764970000,-122.489070000,\\n\\t37.764930000,-122.490150000,\\n\\t37.764920000,-122.490240000,\\n\\t37.764910000,-122.490490000,\\n\\t37.764880000,-122.491210000,\\n\\t37.764830000,-122.492290000,\\n\\t37.764790000,-122.493260000,\\n\\t37.764780000,-122.493350000,\\n\\t37.764740000,-122.494420000,\\n\\t37.764720000,-122.494730000,\\n\\t37.764710000,-122.495500000,\\n\\t37.764700000,-122.495630000,\\n\\t37.764690000,-122.495940000,\\n\\t37.764680000,-122.496260000,\\n\\t37.764670000,-122.496480000,\\n\\t37.764600000,-122.497490000,\\n\\t37.764590000,-122.497650000,\\n\\t37.764550000,-122.498720000,\\n\\t37.764500000,-122.499780000,\\n\\t37.764450000,-122.500870000,\\n\\t37.764440000,-122.501060000,\\n\\t37.764400000,-122.501930000,\\n\\t37.764360000,-122.502990000,\\n\\t37.764310000,-122.504080000,\\n\\t37.764260000,-122.505140000,\\n\\t37.764260000,-122.505250000,\\n\\t37.764220000,-122.506220000,\\n\\t37.764170000,-122.507280000,\\n\\t37.764120000,-122.508360000,\\n\\t37.764100000,-122.508850000,\\n\\t37.764100000,-122.509150000,\\n\\t37.764100000,-122.509440000,\\n\\t37.764090000,-122.509760000,\\n\\t37.764080000,-122.510200000,\\n\\t37.764070000,-122.510320000,\\n\\t37.764060000,-122.510430000,\\n\\t37.764030000,-122.510430000,\\n\\t37.764030000,-122.510430000,\\n\\t37.763960000,-122.510430000,\\n\\t37.763960000,-122.510290000,\\n\\t37.764070000,-122.510320000,\\n\\t37.764170000,-122.510320000,\\n\\t37.764410000,-122.510340000,\\n\\t37.764610000,-122.510350000,\\n\\t37.764780000,-122.510330000,\\n\\t37.764960000,-122.510310000,\\n\\t37.765340000,-122.510280000,\\n\\t37.765570000,-122.510260000,\\n\\t37.765840000,-122.510250000,\\n\\t37.765900000,-122.510250000,\\n\\t37.766140000,-122.510260000,\\n\\t37.766410000,-122.510260000,\\n\\t37.766620000,-122.510270000,\\n\\t37.767040000,-122.510310000,\\n\\t37.767270000,-122.510330000,\\n\\t37.767620000,-122.510390000,\\n\\t37.767670000,-122.510390000,\\n\\t37.767740000,-122.510410000,\\n\\t37.767840000,-122.510430000,\\n\\t37.768280000,-122.510530000,\\n\\t37.768620000,-122.510600000,\\n\\t37.768690000,-122.510620000,\\n\\t37.769020000,-122.510690000,\\n\\t37.769260000,-122.510730000,\\n\\t37.769420000,-122.510750000,\\n\\t37.770010000,-122.510830000,\\n\\t37.770340000,-122.510860000,\\n\\t37.770460000,-122.510870000,\\n\\t37.770930000,-122.510930000,\\n\\t37.770980000,-122.510930000,\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : value + 2)];\"}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" } } ] -} \ No newline at end of file +} From 7c9f8d755fdaf01d3cb644e83b63e36de5771689 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 15:19:48 +0200 Subject: [PATCH 207/261] Dmitriymush table columns assignment gateway (#2429) * added: import of gateway value * expressions changed * fixed refactor rename * changed key and value for translating * feature/: description added to the list of columns assingment and could be imported/exported from now * Delete package-lock.json * Revert package-lock Co-authored-by: Dmitriy Mushat <54553744+Dmitriymush@users.noreply.github.com> --- ui/package-lock.json | 10008 ++++++---------- ui/src/app/api/entity.service.js | 13 +- ui/src/app/common/types.constant.js | 8 + .../import-dialog-csv.controller.js | 9 + .../table-columns-assignment.directive.js | 22 +- .../table-columns-assignment.tpl.html | 5 +- ui/src/app/locale/locale.constant-en_US.json | 4 +- 7 files changed, 3788 insertions(+), 6281 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index 74db613d09..b8368d9820 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -5,61 +5,66 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.5.5.tgz", - "integrity": "sha512-UHI+7pHv/tk9g6WXQKYz+kmXTI77YtuY3vqC59KIqcoWEjsJJSG6rAxKaLsgj3LDyadsPrCB929gVOKM6Hui0w==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.8.4.tgz", + "integrity": "sha512-XXLgAm6LBbaNxaGhMAznXXaxtCWfuv6PIDJ9Alsy9JYTOh+j2jJz+L/162kkfU1j/pTSxK1xGmlwI4pdIMkoag==", "dev": true, "requires": { - "chokidar": "^2.0.4", - "commander": "^2.8.1", + "chokidar": "^2.1.8", + "commander": "^4.0.1", "convert-source-map": "^1.1.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.0.0", "lodash": "^4.17.13", - "mkdirp": "^0.5.1", - "output-file-sync": "^2.0.0", + "make-dir": "^2.1.0", "slash": "^2.0.0", "source-map": "^0.5.0" }, "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true } } }, "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha1-BuKrGb21NThVWaq7W6WXKUgoAPg=", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/compat-data": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", + "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", "dev": true, "requires": { - "@babel/highlight": "^7.0.0" + "browserslist": "^4.8.5", + "invariant": "^2.2.4", + "semver": "^5.5.0" } }, "@babel/core": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.5.5.tgz", - "integrity": "sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helpers": "^7.5.5", - "@babel/parser": "^7.5.5", - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5", - "convert-source-map": "^1.1.0", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", + "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helpers": "^7.8.4", + "@babel/parser": "^7.8.4", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3", + "convert-source-map": "^1.7.0", "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", "json5": "^2.1.0", "lodash": "^4.17.13", "resolve": "^1.3.2", @@ -67,62 +72,6 @@ "source-map": "^0.5.0" }, "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", - "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", - "dev": true, - "requires": { - "@babel/types": "^7.5.5", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", - "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", - "dev": true - }, - "@babel/traverse": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", - "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.5.5", - "@babel/types": "^7.5.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -132,544 +81,266 @@ "ms": "^2.1.1" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true } } }, "@babel/generator": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", - "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", "dev": true, "requires": { - "@babel/types": "^7.4.4", + "@babel/types": "^7.8.3", "jsesc": "^2.5.1", - "lodash": "^4.17.11", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - }, - "dependencies": { - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - } + "lodash": "^4.17.13", + "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", + "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", + "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-explode-assignable-expression": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-builder-react-jsx": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz", - "integrity": "sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.8.3.tgz", + "integrity": "sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ==", "dev": true, "requires": { - "@babel/types": "^7.3.0", + "@babel/types": "^7.8.3", "esutils": "^2.0.0" } }, "@babel/helper-call-delegate": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", - "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz", + "integrity": "sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", + "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.8.4", + "browserslist": "^4.8.5", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", + "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/helper-regex": "^7.8.3", + "regexpu-core": "^4.6.0" } }, "@babel/helper-define-map": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", - "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", + "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.5.5", + "@babel/helper-function-name": "^7.8.3", + "@babel/types": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } } }, "@babel/helper-explode-assignable-expression": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", + "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@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": "sha1-oM6wFoX3M1XUNgwSR/WCv6/I/1M=", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@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": "sha1-g1ctQyDipGVyY3NBE8QoaLZOScM=", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-hoist-variables": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", - "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", + "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", - "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", "dev": true, "requires": { - "@babel/types": "^7.5.5" - }, - "dependencies": { - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } + "@babel/types": "^7.8.3" } }, "@babel/helper-module-imports": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-module-transforms": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", - "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", + "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/template": "^7.4.4", - "@babel/types": "^7.5.5", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } } }, "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.8.3" } }, "@babel/helper-plugin-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", - "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", "dev": true }, "@babel/helper-regex": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.5.5.tgz", - "integrity": "sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", + "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", "dev": true, "requires": { "lodash": "^4.17.13" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } }, "@babel/helper-remap-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", + "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-wrap-function": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-replace-supers": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", - "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", + "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.5.5", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", - "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", - "dev": true, - "requires": { - "@babel/types": "^7.5.5", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", - "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", - "dev": true - }, - "@babel/traverse": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", - "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.5.5", - "@babel/types": "^7.5.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-simple-access": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", "dev": true, "requires": { - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/types": "^7.8.3" } }, "@babel/helper-wrap-function": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", - "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", + "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.2.0" + "@babel/helper-function-name": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/helpers": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.5.5.tgz", - "integrity": "sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", + "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", "dev": true, "requires": { - "@babel/template": "^7.4.4", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz", - "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/generator": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.5.tgz", - "integrity": "sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==", - "dev": true, - "requires": { - "@babel/types": "^7.5.5", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "@babel/parser": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.5.tgz", - "integrity": "sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==", - "dev": true - }, - "@babel/traverse": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.5.tgz", - "integrity": "sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.5.5", - "@babel/types": "^7.5.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.8.4", + "@babel/types": "^7.8.3" } }, "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha1-9xDDjI1Fjm3ZogGvtjf8t4HOmeQ=", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", "dev": true, "requires": { "chalk": "^2.0.0", @@ -680,7 +351,7 @@ "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -697,16 +368,10 @@ "supports-color": "^5.3.0" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -715,905 +380,688 @@ } }, "@babel/node": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.5.5.tgz", - "integrity": "sha512-xsW6il+yY+lzXMsQuvIJNA7tU8ix/f4G6bDt4DrnCkVpsR6clk9XgEbp7QF+xGNDdoD7M7QYokCH83pm+UjD0w==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.8.4.tgz", + "integrity": "sha512-MlczXI/VYRnoaWHjicqrzq2z4DhRPaWQIC+C3ISEQs5z+mEccBsn7IAI5Q97ZDTnFYw6ts5IUTzqArilC/g7nw==", "dev": true, "requires": { - "@babel/polyfill": "^7.0.0", - "@babel/register": "^7.5.5", - "commander": "^2.8.1", + "@babel/register": "^7.8.3", + "commander": "^4.0.1", + "core-js": "^3.2.1", "lodash": "^4.17.13", "node-environment-flags": "^1.0.5", + "regenerator-runtime": "^0.13.3", + "resolve": "^1.13.1", "v8flags": "^3.1.1" }, "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", "dev": true } } }, "@babel/parser": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", - "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", - "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", + "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0", - "@babel/plugin-syntax-async-generators": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", - "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", + "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", - "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", + "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz", - "integrity": "sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz", + "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz", - "integrity": "sha512-j1NwnOqMG9mFUOH58JTFsA/+ZYzQLUZ/drqWUqxCYLGeu2JFZL8YrNC9hBxKmWtAuOCHPcRpgv7fhap09Fb4kA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz", + "integrity": "sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", - "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - } + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", - "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", - "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", - "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-jsx": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz", - "integrity": "sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", + "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", + "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", - "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", + "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", - "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", + "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-remap-async-to-generator": "^7.8.3" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", - "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", + "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz", - "integrity": "sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", + "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-plugin-utils": "^7.8.3", "lodash": "^4.17.13" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } }, "@babel/plugin-transform-classes": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", - "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5", - "@babel/helper-split-export-declaration": "^7.4.4", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", + "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-define-map": "^7.8.3", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - } } }, "@babel/plugin-transform-computed-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", - "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", + "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-destructuring": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz", - "integrity": "sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz", + "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.4.4.tgz", - "integrity": "sha512-P05YEhRc2h53lZDjRPk/OektxCVevFzZs2Gfjd545Wde3k+yFDbXORgl2e0xpbq8mLcKJ7Idss4fAg0zORN/zg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", + "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", - "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - } + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", - "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", + "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", - "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", + "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-for-of": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", - "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", + "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-function-name": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", - "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", + "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", - "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", + "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", - "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", + "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", - "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz", + "integrity": "sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz", - "integrity": "sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz", + "integrity": "sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-simple-access": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", - "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz", + "integrity": "sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/helper-plugin-utils": "^7.0.0", + "@babel/helper-hoist-variables": "^7.8.3", + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", - "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz", + "integrity": "sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-module-transforms": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz", - "integrity": "sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", + "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", "dev": true, "requires": { - "regexp-tree": "^0.1.6" + "@babel/helper-create-regexp-features-plugin": "^7.8.3" } }, "@babel/plugin-transform-new-target": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", - "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", + "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-object-super": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", - "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", + "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.3" } }, "@babel/plugin-transform-parameters": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", - "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz", + "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.4.4", - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-call-delegate": "^7.8.3", + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-property-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", - "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", + "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-react-display-name": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz", - "integrity": "sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.8.3.tgz", + "integrity": "sha512-3Jy/PCw8Fe6uBKtEgz3M82ljt+lTg+xJaM4og+eyu83qLT87ZUSckn0wy7r31jflURWLO83TW6Ylf7lyXj3m5A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-react-jsx": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz", - "integrity": "sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.8.3.tgz", + "integrity": "sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g==", "dev": true, "requires": { - "@babel/helper-builder-react-jsx": "^7.3.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" + "@babel/helper-builder-react-jsx": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" } }, "@babel/plugin-transform-react-jsx-self": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz", - "integrity": "sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.8.3.tgz", + "integrity": "sha512-01OT7s5oa0XTLf2I8XGsL8+KqV9lx3EZV+jxn/L2LQ97CGKila2YMroTkCEIE0HV/FF7CMSRsIAybopdN9NTdg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" } }, "@babel/plugin-transform-react-jsx-source": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz", - "integrity": "sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.8.3.tgz", + "integrity": "sha512-PLMgdMGuVDtRS/SzjNEQYUT8f4z1xb2BAT54vM1X5efkVuYBf5WyGUMbpmARcfq3NaglIwz08UVQK4HHHbC6ag==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-syntax-jsx": "^7.8.3" } }, "@babel/plugin-transform-regenerator": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", - "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz", + "integrity": "sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA==", "dev": true, "requires": { "regenerator-transform": "^0.14.0" - }, - "dependencies": { - "regenerator-transform": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.0.tgz", - "integrity": "sha512-rtOelq4Cawlbmq9xuMR5gdFmv7ku/sFoB7sRiywx7aq53bc52b4j6zvH7Te1Vt/X2YveDKnCGUbioieU7FEL3w==", - "dev": true, - "requires": { - "private": "^0.1.6" - } - } } }, "@babel/plugin-transform-reserved-words": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", - "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", + "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", - "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", + "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-spread": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz", - "integrity": "sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", + "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", - "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", + "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/helper-regex": "^7.8.3" } }, "@babel/plugin-transform-template-literals": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", - "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", + "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", - "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", + "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.4.4.tgz", - "integrity": "sha512-il+/XdNw01i93+M9J9u4T7/e/Ue/vWfNZE4IRUQjplu2Mqb/AFTDimkw2tdEdSH50wuQXZAbXSql0UphQke+vA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.5.4" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true - }, - "regexpu-core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.5.4.tgz", - "integrity": "sha512-BtizvGtFQKGPUcTy56o3nk1bGRp4SZOTYrDtGNlqCQufptV5IkkLN6Emw+yunAJjzf+C9FQFtvq7IoA3+oMYHQ==", - "dev": true, - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.0.2", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", - "dev": true - }, - "regjsparser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.0.tgz", - "integrity": "sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ==", - "dev": true, - "requires": { - "jsesc": "~0.5.0" - } - } - } - }, - "@babel/polyfill": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", - "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", + "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", "dev": true, "requires": { - "core-js": "^2.6.5", - "regenerator-runtime": "^0.13.2" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", - "dev": true - } + "@babel/helper-create-regexp-features-plugin": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3" } }, "@babel/preset-env": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.5.5.tgz", - "integrity": "sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.5.5", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.5.5", - "@babel/plugin-transform-classes": "^7.5.5", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.5.0", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.5.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.4.5", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.5.5", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.2.0", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.4.4", - "@babel/types": "^7.5.5", - "browserslist": "^4.6.0", - "core-js-compat": "^3.1.1", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz", + "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.8.4", + "@babel/helper-compilation-targets": "^7.8.4", + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-proposal-async-generator-functions": "^7.8.3", + "@babel/plugin-proposal-dynamic-import": "^7.8.3", + "@babel/plugin-proposal-json-strings": "^7.8.3", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.8.3", + "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", + "@babel/plugin-proposal-optional-chaining": "^7.8.3", + "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.8.3", + "@babel/plugin-transform-arrow-functions": "^7.8.3", + "@babel/plugin-transform-async-to-generator": "^7.8.3", + "@babel/plugin-transform-block-scoped-functions": "^7.8.3", + "@babel/plugin-transform-block-scoping": "^7.8.3", + "@babel/plugin-transform-classes": "^7.8.3", + "@babel/plugin-transform-computed-properties": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.8.3", + "@babel/plugin-transform-dotall-regex": "^7.8.3", + "@babel/plugin-transform-duplicate-keys": "^7.8.3", + "@babel/plugin-transform-exponentiation-operator": "^7.8.3", + "@babel/plugin-transform-for-of": "^7.8.4", + "@babel/plugin-transform-function-name": "^7.8.3", + "@babel/plugin-transform-literals": "^7.8.3", + "@babel/plugin-transform-member-expression-literals": "^7.8.3", + "@babel/plugin-transform-modules-amd": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.8.3", + "@babel/plugin-transform-modules-systemjs": "^7.8.3", + "@babel/plugin-transform-modules-umd": "^7.8.3", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", + "@babel/plugin-transform-new-target": "^7.8.3", + "@babel/plugin-transform-object-super": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.8.4", + "@babel/plugin-transform-property-literals": "^7.8.3", + "@babel/plugin-transform-regenerator": "^7.8.3", + "@babel/plugin-transform-reserved-words": "^7.8.3", + "@babel/plugin-transform-shorthand-properties": "^7.8.3", + "@babel/plugin-transform-spread": "^7.8.3", + "@babel/plugin-transform-sticky-regex": "^7.8.3", + "@babel/plugin-transform-template-literals": "^7.8.3", + "@babel/plugin-transform-typeof-symbol": "^7.8.4", + "@babel/plugin-transform-unicode-regex": "^7.8.3", + "@babel/types": "^7.8.3", + "browserslist": "^4.8.5", + "core-js-compat": "^3.6.2", "invariant": "^2.2.2", - "js-levenshtein": "^1.1.3", + "levenary": "^1.1.1", "semver": "^5.5.0" - }, - "dependencies": { - "@babel/types": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.5.tgz", - "integrity": "sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" - } - }, - "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" - } - }, - "caniuse-lite": { - "version": "1.0.30000984", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000984.tgz", - "integrity": "sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.194.tgz", - "integrity": "sha512-w0LHR2YD9Ex1o+Sz4IN2hYzCB8vaFtMNW+yJcBf6SZlVqgFahkne/4rGVJdk4fPF98Gch9snY7PiabOh+vqHNg==", - "dev": true - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } } }, "@babel/preset-react": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.0.0.tgz", - "integrity": "sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.8.3.tgz", + "integrity": "sha512-9hx0CwZg92jGb7iHYQVgi0tOEHP/kM60CtWJQnmbATSPIQQ2xYzfoCI3EdqAhFBeeJwYMdWQuDUHMsuDbH9hyQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0" + "@babel/helper-plugin-utils": "^7.8.3", + "@babel/plugin-transform-react-display-name": "^7.8.3", + "@babel/plugin-transform-react-jsx": "^7.8.3", + "@babel/plugin-transform-react-jsx-self": "^7.8.3", + "@babel/plugin-transform-react-jsx-source": "^7.8.3" } }, "@babel/register": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.5.5.tgz", - "integrity": "sha512-pdd5nNR+g2qDkXZlW1yRCWFlNrAn2PPdnZUB72zjX4l1Vv4fMRRLwyf+n/idFCLI1UgVGboUU8oVziwTBiyNKQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.8.3.tgz", + "integrity": "sha512-t7UqebaWwo9nXWClIPLPloa5pN33A2leVs8Hf0e9g9YwUP8/H9NeR7DJU+4CXo23QtjChQv5a3DjEtT83ih1rg==", "dev": true, "requires": { - "core-js": "^3.0.0", "find-cache-dir": "^2.0.0", "lodash": "^4.17.13", - "mkdirp": "^0.5.1", + "make-dir": "^2.1.0", "pirates": "^4.0.0", - "source-map-support": "^0.5.9" - }, - "dependencies": { - "core-js": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.1.4.tgz", - "integrity": "sha512-YNZN8lt82XIMLnLirj9MhKDFZHalwzzrL9YLt6eb0T5D0EDl4IQ90IGkua8mHbnxNrkj1d8hbdizMc0Qmg1WnQ==", - "dev": true - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } + "source-map-support": "^0.5.16" } }, "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", "requires": { "regenerator-runtime": "^0.13.2" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" } } }, "@babel/template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", - "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" } }, "@babel/traverse": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", - "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.4.4", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.4.5", - "@babel/types": "^7.4.4", + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.11" + "lodash": "^4.17.13" }, "dependencies": { "debug": { @@ -1625,12 +1073,6 @@ "ms": "^2.1.1" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1640,45 +1082,71 @@ } }, "@babel/types": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", - "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", "dev": true, "requires": { "esutils": "^2.0.2", - "lodash": "^4.17.11", + "lodash": "^4.17.13", "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - } } }, "@flowjs/ng-flow": { "version": "2.7.8", "resolved": "https://registry.npmjs.org/@flowjs/ng-flow/-/ng-flow-2.7.8.tgz", - "integrity": "sha1-HZ+dH4Ks2lNgMowxW6z9YNv9mBk=" + "integrity": "sha512-zO6jNvz41oMOJj9+1N+vLT0ytitbCtuGABJQRzQDOPXyRMmlSXfJ7om5oYOztyUFrr4jDpE4QFPt+r2/RFceCg==" }, "@mrmlnc/readdir-enhanced": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha1-UkryQNGjYFJ7cwR17PoTRKpUDd4=", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", "dev": true, "requires": { "call-me-maybe": "^1.0.1", "glob-to-regexp": "^0.3.0" } }, + "@nodelib/fs.scandir": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", + "integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.3", + "run-parallel": "^1.1.9" + }, + "dependencies": { + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true + } + } + }, "@nodelib/fs.stat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@nodelib/fs.walk": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz", + "integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.3", + "fastq": "^1.6.0" + } + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1697,9 +1165,9 @@ } }, "@types/jquery": { - "version": "3.3.30", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.30.tgz", - "integrity": "sha512-chB+QbLulamShZAFcTJtl8opZwHFBpDOP6nRLrPGkhC6N1aKWrDXg2Nc71tEg6ny6E8SQpRwbWSi9GdstH5VJA==", + "version": "3.3.32", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.32.tgz", + "integrity": "sha512-UKoof2mnV/X1/Ix2g+V2Ny5sgHjV8nK/UJbiYxuo4zPwzGyFlZ/mp4KaePb2VqQrqJctmcDQNA57buU84/2uIw==", "requires": { "@types/sizzle": "*" } @@ -1710,10 +1178,28 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", "dev": true }, + "@types/minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", + "dev": true + }, "@types/node": { - "version": "12.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.10.tgz", - "integrity": "sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ==", + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.2.tgz", + "integrity": "sha512-uvilvAQbdJvnSBFcKJ2td4016urcGvsiR+N4dHGU87ml8O2Vl6l+ErOi9w0kXSPiwJ1AYlIW+0pDXDWWMOiWbw==", + "dev": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, "@types/sizzle": { @@ -1721,6 +1207,32 @@ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==" }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-2.0.0.tgz", + "integrity": "sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==", + "dev": true, + "requires": { + "vfile-message": "*" + } + }, "@webassemblyjs/ast": { "version": "1.8.5", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", @@ -1912,7 +1424,7 @@ "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha1-+PLIh60Qv2f2NPAFtph/7TF5qsg=" + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, "accepts": { "version": "1.3.7", @@ -1925,27 +1437,21 @@ } }, "acorn": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.2.0.tgz", - "integrity": "sha512-8oe72N3WPMjA+2zVG71Ia0nXZ8DpQH+QyyHO+p06jT8eg8FGG3FbcUIi8KziHlAfheJQZeoqbvq1mQSQHXKYLw==", - "dev": true - }, - "acorn-dynamic-import": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", - "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, "acorn-jsx": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", - "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.1.1.tgz", + "integrity": "sha512-wdlPY2tm/9XBr7QkKlq0WQVgiuGTX6YWPyRyBviSoScBuLfTVQhvwg6wJ369GJ/1nPfTLMfnrFIfjqVg6d+jQQ==", "dev": true }, "add-dom-event-listener": { @@ -1956,13 +1462,23 @@ "object-assign": "4.x" } }, + "aggregate-error": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz", + "integrity": "sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" @@ -2018,7 +1534,7 @@ "angular-carousel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/angular-carousel/-/angular-carousel-1.1.0.tgz", - "integrity": "sha1-PmlA5ovRio85L8Qx2XGSrDSIMdE=" + "integrity": "sha512-UiLMgT7Ueqk4xpliF1gWt4dYKXezdJA1jyZPNsUWkOGO/dwLuKi284h3BgWl4CnaH7kEBw8L2gsBOyqbYaumNQ==" }, "angular-cookies": { "version": "1.5.8", @@ -2039,7 +1555,7 @@ } }, "angular-fullscreen": { - "version": "git://github.com/fabiobiondi/angular-fullscreen.git#8217174565761d3566807bc60a73b5ca015b8cb6", + "version": "git://github.com/fabiobiondi/angular-fullscreen.git#119b7fbac911d154fd56ace38ebe3432475e8a20", "from": "git://github.com/fabiobiondi/angular-fullscreen.git#master" }, "angular-gridster": { @@ -2113,7 +1629,7 @@ "angular-translate": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", - "integrity": "sha1-sp7Q0vm6xEB156rTKEFmxZ4VB5E=", + "integrity": "sha512-Mw0kFBqsv5j8ItL9IhRZunIlVmIRW6iFsiTmRs9wGr2QTt8z4rehYlWyHos8qnXc/kyOYJiW50iH50CSNHGB9A==", "requires": { "angular": ">=1.2.26 <=1.7" } @@ -2121,7 +1637,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": "sha1-icu1mCeALYb4EVJ1+/iNbYiWsNQ=", + "integrity": "sha512-TyKzCW4GubNazwCgLpCVXd2212CWdZOckf+aL5+gLuThPhVpOvlg18RSmz8MNPto3kwCcCw3LzShlZ6RX/MQRA==", "requires": { "angular-translate": "~2.18.1" } @@ -2129,7 +1645,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": "sha1-FsUq4MYcJA8PJBZKBSGUPPi6QI4=", + "integrity": "sha512-SlmyxLB/UUy7FWoGx5QJHrhq8fUu/xzCR0h/ngexOtXZopQjs1vm+TrFZ69d4c/LI7C91sfP4mq4ES29o1xCxA==", "requires": { "angular-translate": "~2.18.1", "messageformat": "~1.0.2" @@ -2138,7 +1654,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": "sha1-rQw8iDsYsIm9uNsCu9Nm2QP4V8w=", + "integrity": "sha512-5MuyzAROfc493kjLjKlLGLBzXiRmZIFbcWZGutDRxW5SRXSpwrH0u0hh0ENNnUyUQbe2vUspHNPIuZqlq8qIhw==", "requires": { "angular-translate": "~2.18.1" } @@ -2146,7 +1662,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": "sha1-j8vaspb6gkkOALQorxp0ahf0QVY=", + "integrity": "sha512-wiMaF/0OGN/3ilaYunfsqdLNpfGZEJK0fj4zT8yjD3XPq7Q9kM88xZ4XJiWKgodZShBljGCRzqgQbKMF7d1MLw==", "requires": { "angular-cookies": ">=1.2.26 <1.8", "angular-translate": "~2.18.1" @@ -2155,7 +1671,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": "sha1-lHQP5NgBq3gpopofBeHDkFTIcwM=", + "integrity": "sha512-zPxcbIJ8tdWXtWNKLtaswynKid0w5le6WPMwiLWhgKPnyzOp/y5WLBW+JEfnZnkGE24yOGhJ6jVPgRNzelLgzg==", "requires": { "angular-translate": "~2.18.1", "angular-translate-storage-cookie": "~2.18.1" @@ -2234,7 +1750,7 @@ "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha1-aALmJk79GMeQobDVF/DyYnvyyUo=", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true }, "are-we-there-yet": { @@ -2250,7 +1766,7 @@ "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { "sprintf-js": "~1.0.2" @@ -2265,7 +1781,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "arr-union": { @@ -2287,13 +1803,14 @@ "dev": true }, "array-includes": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", - "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.7.0" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" } }, "array-union": { @@ -2317,6 +1834,16 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", @@ -2366,7 +1893,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -2379,8 +1906,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true + "dev": true }, "assign-symbols": { "version": "1.0.0", @@ -2401,9 +1927,9 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async/-/async-3.1.1.tgz", + "integrity": "sha512-X5Dj8hK1pJNC2Wzo2Rcp9FBVdJMGRR/S7V+lH46s8GVFhtbo5O4Le5GECCF/8PISVdkUA6mMPvgz7qTTD1rf1g==" }, "async-each": { "version": "1.0.3", @@ -2417,6 +1943,12 @@ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=", "dev": true }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2426,7 +1958,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha1-bZUX654DDSQ2ZmZR6GvZ9vE1M8k=", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, "attr-accept": { @@ -2438,17 +1970,18 @@ } }, "autoprefixer": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", - "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", + "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", "dev": true, "requires": { - "browserslist": "^2.11.3", - "caniuse-lite": "^1.0.30000805", + "browserslist": "^4.8.3", + "caniuse-lite": "^1.0.30001020", + "chalk": "^2.4.2", "normalize-range": "^0.1.2", "num2fraction": "^1.2.2", - "postcss": "^6.0.17", - "postcss-value-parser": "^3.2.3" + "postcss": "^7.0.26", + "postcss-value-parser": "^4.0.2" }, "dependencies": { "ansi-styles": { @@ -2460,16 +1993,6 @@ "color-convert": "^1.9.0" } }, - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -2481,29 +2004,6 @@ "supports-color": "^5.3.0" } }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -2519,39 +2019,26 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "optional": true + "dev": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, "babel-eslint": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.2.tgz", - "integrity": "sha512-UdsurWPtgiPgpJ06ryUnuaSXC2s0WoSZnQmEpbAH65XZSdwowgN5MvyP7e88nW07FYXv72erVtpBkxyDVKhH1Q==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/parser": "^7.0.0", "@babel/traverse": "^7.0.0", "@babel/types": "^7.0.0", - "eslint-scope": "3.7.1", - "eslint-visitor-keys": "^1.0.0" - }, - "dependencies": { - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - } + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" } }, "babel-loader": { @@ -2564,72 +2051,6 @@ "loader-utils": "^1.0.2", "mkdirp": "^0.5.1", "pify": "^4.0.1" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } } }, "babel-plugin-dynamic-import-node": { @@ -2668,9 +2089,9 @@ } }, "bail": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.4.tgz", - "integrity": "sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", "dev": true }, "balanced-match": { @@ -2681,7 +2102,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -2705,7 +2126,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2714,7 +2135,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -2723,32 +2144,20 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true } } }, "base64-js": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", - "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" }, "batch": { "version": "0.6.1", @@ -2766,9 +2175,9 @@ } }, "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha1-pfwpi4G54Nyi5FiCR4S2XFK6WI4=", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, "binary-extensions": { @@ -2782,6 +2191,16 @@ "resolved": "https://registry.npmjs.org/bind-decorator/-/bind-decorator-1.0.11.tgz", "integrity": "sha1-5BvAah9l3ZzsR2yRxdrzl4SIJS8=" }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", @@ -2792,9 +2211,9 @@ } }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "bn.js": { @@ -2871,7 +2290,7 @@ "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2984,20 +2403,20 @@ } }, "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", + "version": "4.8.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.7.tgz", + "integrity": "sha512-gFOnZNYBHrEyUML0xr5NJ6edFaaKbTFX9S9kQHlYfCP0Rit/boRIz4G+Avq6/4haEKJXdGGUnoolx+5MWW2BoA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" + "caniuse-lite": "^1.0.30001027", + "electron-to-chromium": "^1.3.349", + "node-releases": "^1.1.49" } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -3036,31 +2455,35 @@ "dev": true }, "cacache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.3.tgz", - "integrity": "sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==", + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", "dev": true, "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", + "chownr": "^1.1.2", "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.1.15", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", "mkdirp": "^0.5.1", "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3081,9 +2504,9 @@ } }, "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } @@ -3091,7 +2514,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -3103,14 +2526,6 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "call-me-maybe": { @@ -3130,12 +2545,21 @@ "dependencies": { "callsites": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", "dev": true } } }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3153,9 +2577,9 @@ } }, "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, "camelcase-keys": { @@ -3166,12 +2590,20 @@ "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=", + "dev": true + } } }, "caniuse-lite": { - "version": "1.0.30000984", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000984.tgz", - "integrity": "sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==", + "version": "1.0.30001028", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001028.tgz", + "integrity": "sha512-Vnrq+XMSHpT7E+LWoIYhs3Sne8h9lx9YJV3acH3THNCwU/9zV93/ta4xVfzTtnqd3rvnuVpVjE3DFqf56tr3aQ==", "dev": true }, "canvas-gauges": { @@ -3186,9 +2618,9 @@ "dev": true }, "ccount": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.4.tgz", - "integrity": "sha512-fpZ81yYfzentuieinmGnphk0pLkOTMm6MZdVqwd77ROvhko6iujLNGrHH5E7utq3ygWklwfmwuG+A7P+NpqT6w==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", "dev": true }, "chain-function": { @@ -3214,27 +2646,27 @@ "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, "character-entities": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.3.tgz", - "integrity": "sha512-yB4oYSAa9yLcGyTbB4ItFwHw43QHdH129IJ5R+WvxOkWlyFnR5FAaBNnUq4mcxsTVZGh28bHoeTHMKXH1wZf3w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", "dev": true }, "character-entities-html4": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.3.tgz", - "integrity": "sha512-SwnyZ7jQBCRHELk9zf2CN5AnGEc2nA+uKMZLHvcqhpPprjkYhiLn0DywMHgN5ttFZuITMATbh68M6VIVKwJbcg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", "dev": true }, "character-entities-legacy": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.3.tgz", - "integrity": "sha512-YAxUpPoPwxYFsslbdKkhrGnXAtXoHNgYjlBM3WMXkWGTl5RsY3QmOyhwAgL8Nxm9l5LBThXGawxKPn68y6/fww==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", "dev": true }, "character-reference-invalid": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.3.tgz", - "integrity": "sha512-VOq6PRzQBam/8Jm6XBGk2fNEnHXAdGd6go0rtd4weAGECBamHDwwCQSOT12TACIYUZegUXnV6xBXqUssijtxIg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", "dev": true }, "chardet": { @@ -3243,9 +2675,9 @@ "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" }, "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -3263,9 +2695,9 @@ } }, "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true }, "chrome-trace-event": { @@ -3287,16 +2719,10 @@ "safe-buffer": "^5.0.1" } }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "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": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -3313,12 +2739,6 @@ "requires": { "is-descriptor": "^0.1.0" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, @@ -3328,9 +2748,9 @@ "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" }, "clean-css": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", - "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", + "integrity": "sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==", "dev": true, "requires": { "source-map": "~0.6.0" @@ -3344,6 +2764,12 @@ } } }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -3406,42 +2832,23 @@ "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" }, "clone-deep": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz", - "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "for-own": "^1.0.0", "is-plain-object": "^2.0.4", - "kind-of": "^6.0.0", - "shallow-clone": "^1.0.0" - }, - "dependencies": { - "for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" } }, "clone-regexp": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.1.tgz", - "integrity": "sha1-BRgFzTMXM3XYIRj8CRhgbaOf1g8=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", "dev": true, "requires": { - "is-regexp": "^1.0.0", - "is-supported-regexp-flag": "^1.0.0" + "is-regexp": "^2.0.0" } }, "code-point-at": { @@ -3451,9 +2858,9 @@ "dev": true }, "collapse-white-space": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.5.tgz", - "integrity": "sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", "dev": true }, "collection-visit": { @@ -3491,9 +2898,9 @@ } }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "commondir": { "version": "1.0.1", @@ -3526,12 +2933,12 @@ "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" }, "compressible": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", - "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "requires": { - "mime-db": ">= 1.40.0 < 2" + "mime-db": ">= 1.43.0 < 2" } }, "compression": { @@ -3550,23 +2957,23 @@ } }, "compression-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-ls+oKw4eRbvaSv/hj9NmctihhBcR26j76JxV0bLRLcWhrUBdQFgd06z/Kgg7exyQvtWWP484wZxs0gIUX3NO0Q==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-3.1.0.tgz", + "integrity": "sha512-iqTHj3rADN4yHwXMBrQa/xrncex/uEQy8QHlaTKxGchT/hC0SdlJlmL/5eRqffmWq2ep0/Romw6Ld39JjTR/ug==", "dev": true, "requires": { - "cacache": "^11.2.0", + "cacache": "^13.0.1", "find-cache-dir": "^3.0.0", "neo-async": "^2.5.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^1.4.0", + "schema-utils": "^2.6.1", + "serialize-javascript": "^2.1.2", "webpack-sources": "^1.0.1" }, "dependencies": { "find-cache-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.0.0.tgz", - "integrity": "sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.2.0.tgz", + "integrity": "sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==", "dev": true, "requires": { "commondir": "^1.0.1", @@ -3584,6 +2991,33 @@ "path-exists": "^4.0.0" } }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", + "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3598,6 +3032,12 @@ "requires": { "find-up": "^4.0.0" } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true } } }, @@ -3609,7 +3049,7 @@ "concat-stream": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha1-kEvfGUzTEi/Gdcd/xKw9T/D9GjQ=", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -3634,13 +3074,10 @@ "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "console-control-strings": { "version": "1.1.0", @@ -3672,13 +3109,13 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -3699,7 +3136,7 @@ "copy-concurrently": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha1-kilzmMrjSTf8r9bsgTnBgFHwteA=", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", "dev": true, "requires": { "aproba": "^1.1.1", @@ -3717,12 +3154,12 @@ "dev": true }, "copy-webpack-plugin": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.0.3.tgz", - "integrity": "sha512-PlZRs9CUMnAVylZq+vg2Juew662jWtwOXOqH4lbQD9ZFhRG9R7tVStOgHt21CBGVq7k5yIJaz8TXDLSjV+Lj8Q==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", "dev": true, "requires": { - "cacache": "^11.3.2", + "cacache": "^12.0.3", "find-cache-dir": "^2.1.0", "glob-parent": "^3.1.0", "globby": "^7.1.1", @@ -3730,36 +3167,39 @@ "loader-utils": "^1.2.3", "minimatch": "^3.0.4", "normalize-path": "^3.0.0", - "p-limit": "^2.2.0", + "p-limit": "^2.2.1", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "webpack-log": "^2.0.0" }, "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -3770,180 +3210,70 @@ "path-is-absolute": "^1.0.0" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "yallist": "^3.0.2" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "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==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "figgy-pudding": "^3.5.1" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } } } }, "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" }, "core-js-compat": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.1.4.tgz", - "integrity": "sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==", + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", + "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", "dev": true, "requires": { - "browserslist": "^4.6.2", - "core-js-pure": "3.1.4", - "semver": "^6.1.1" + "browserslist": "^4.8.3", + "semver": "7.0.0" }, "dependencies": { - "browserslist": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.6.6.tgz", - "integrity": "sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000984", - "electron-to-chromium": "^1.3.191", - "node-releases": "^1.1.25" - } - }, - "caniuse-lite": { - "version": "1.0.30000984", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000984.tgz", - "integrity": "sha512-n5tKOjMaZ1fksIpQbjERuqCyfgec/m9pferkFQbLmWtqLUdmt12hNhjSwsmPdqeiG2NkITOQhr1VYIwWSAceiA==", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.194.tgz", - "integrity": "sha512-w0LHR2YD9Ex1o+Sz4IN2hYzCB8vaFtMNW+yJcBf6SZlVqgFahkne/4rGVJdk4fPF98Gch9snY7PiabOh+vqHNg==", - "dev": true - }, "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true } } }, - "core-js-pure": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.1.4.tgz", - "integrity": "sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, "cosmiconfig": { "version": "5.2.1", @@ -3957,20 +3287,14 @@ "parse-json": "^4.0.0" }, "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha1-E7BM2z5sXRnfkatph6hpVhmwqnE=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" } }, "parse-json": { @@ -3982,6 +3306,12 @@ "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true } } }, @@ -4025,7 +3355,7 @@ "create-react-class": { "version": "15.6.3", "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha1-LXMjf7P5cK5uvgEanmb0bbyoADY=", + "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", "requires": { "fbjs": "^0.8.9", "loose-envify": "^1.3.1", @@ -4033,13 +3363,12 @@ } }, "cross-env": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.0.tgz", - "integrity": "sha512-jtdNFfFW1hB7sMhr/H6rW1Z45LFqyI431m3qU6bFXcQ3Eh7LtBuG3h74o7ohHZ3crrRkkqHlo4jYHFPcjroANg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", + "integrity": "sha512-1yHhtcfAd1r4nwQgknowuUNfIT9E8dOMMspC36g45dN+iD1blloi7xp8X/xAIDnjHWyt1uQ8PHk2fkNaym7soQ==", "dev": true, "requires": { - "cross-spawn": "^6.0.5", - "is-windows": "^1.0.0" + "cross-spawn": "^6.0.5" } }, "cross-spawn": { @@ -4075,18 +3404,18 @@ } }, "css-animation": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.5.0.tgz", - "integrity": "sha512-hWYoWiOZ7Vr20etzLh3kpWgtC454tW5vn4I6rLANDgpzNSkO7UfOqyCEeaoBSG9CYWQpRkFWTWbWW8o3uZrNLw==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", "requires": { "babel-runtime": "6.x", "component-classes": "^1.2.5" } }, "css-loader": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.1.0.tgz", - "integrity": "sha512-MuL8WsF/KSrHCBCYaozBKlx+r7vIfUaDTEreo7wR7Vv3J6N0z6fqWjRk3e/6wjneitXN1r/Y9FTK1psYNOBdJQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.4.2.tgz", + "integrity": "sha512-jYq4zdZT0oS0Iykt+fqnzVLRIeiPWhka+7BqPn+oSIpWJAHak5tmB/WZrJ2a21JhCeFyNnnlroSl8c+MtVndzA==", "dev": true, "requires": { "camelcase": "^5.3.1", @@ -4094,37 +3423,13 @@ "icss-utils": "^4.1.1", "loader-utils": "^1.2.3", "normalize-path": "^3.0.0", - "postcss": "^7.0.17", + "postcss": "^7.0.23", "postcss-modules-extract-imports": "^2.0.0", "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.1.0", + "postcss-modules-scope": "^2.1.1", "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.0.0", - "schema-utils": "^2.0.0" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "schema-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz", - "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } + "postcss-value-parser": "^4.0.2", + "schema-utils": "^2.6.0" } }, "css-select": { @@ -4161,9 +3466,9 @@ } }, "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, "dashdash": { @@ -4173,26 +3478,12 @@ "dev": true, "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha1-XRKFFd8TT/Mn6QpMk/Tgd6U2NB8=", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -4221,10 +3512,18 @@ "dev": true }, "deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } }, "deep-is": { "version": "0.1.3", @@ -4240,61 +3539,6 @@ "requires": { "execa": "^1.0.0", "ip-regex": "^2.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } } }, "defaults": { @@ -4317,7 +3561,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -4327,7 +3571,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4336,7 +3580,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4345,25 +3589,13 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true } } }, @@ -4382,10 +3614,31 @@ "rimraf": "^2.6.3" }, "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", "dev": true } } @@ -4399,7 +3652,7 @@ "delegate": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", - "integrity": "sha1-tmtxwxWFIuirV0T3INjKDCr1kWY=" + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, "delegates": { "version": "1.0.0", @@ -4414,9 +3667,9 @@ "dev": true }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -4459,29 +3712,12 @@ "dev": true, "requires": { "path-type": "^3.0.0" - }, - "dependencies": { - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } } }, "directory-tree": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-2.2.3.tgz", - "integrity": "sha512-o2D5lYpQpsSCa2w9/NmGZ/d0GJhfa6+8aqLjeoYgVYIG8VViyom6MNvcuHvrcqJcOyS/IoZw4SO0JNq7QPjJOg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/directory-tree/-/directory-tree-2.2.4.tgz", + "integrity": "sha512-2N43msQptKbi3WMfIs+U09yi6bfyKL+MWyj5VMj8t1F/Tx04bt1cn/EEIU3o1JBltlJk7NQnzOEuTNa/KQvbWA==", "dev": true }, "dns-equal": { @@ -4519,9 +3755,9 @@ } }, "dom-align": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.8.3.tgz", - "integrity": "sha512-thE1qB8mvtRZgwN4+IGFz1rv7zVsr08c2/IEYtOJIeTzW4YDadIOd5nQ4BpiiAvUWg55xTeGq7zLTDxDYWDrnw==" + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.10.4.tgz", + "integrity": "sha512-wytDzaru67AmqFOY4B9GUb/hrwWagezoYYK97D/vpK+ezg+cnuZO0Q2gltUPa7KfNmIqfRIYVCF8UhRDEHAmgQ==" }, "dom-converter": { "version": "0.2.0", @@ -4546,13 +3782,21 @@ "integrity": "sha1-6PNnMt0ImwIBqI14Fdw/iObWbH4=" }, "dom-serializer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", - "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", "dev": true, "requires": { - "domelementtype": "^1.3.0", - "entities": "^1.1.1" + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + } } }, "dom-walk": { @@ -4564,7 +3808,7 @@ "domain-browser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha1-PTH1AZGmdJ3RN1p/Ui6CPULlTto=", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, "domelementtype": { @@ -4593,12 +3837,12 @@ } }, "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.2.0.tgz", + "integrity": "sha512-uEUyaDKoSQ1M4Oq8l45hSE26SnTxL6snNnqvK/VWx5wJhmff5z0FUVJDKDanor/6w3kzE3i7XZOk+7wC0EXr1A==", "dev": true, "requires": { - "is-obj": "^1.0.0" + "is-obj": "^2.0.0" } }, "duplexify": { @@ -4641,15 +3885,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.194", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.194.tgz", - "integrity": "sha512-w0LHR2YD9Ex1o+Sz4IN2hYzCB8vaFtMNW+yJcBf6SZlVqgFahkne/4rGVJdk4fPF98Gch9snY7PiabOh+vqHNg==", + "version": "1.3.355", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.355.tgz", + "integrity": "sha512-zKO/wS+2ChI/jz9WAo647xSW8t2RmgRLFdbUb/77cORkUTargO+SCj4ctTHjBn2VeNFrsLgDT7IuDVrd3F8mLQ==", "dev": true }, "elliptic": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.0.tgz", - "integrity": "sha512-eFOJTMyCYb7xtE/caJ6JJu+bhi67WCYNbkGSknu20pmM8Ke/bqOfdnZWxyoGN26JgfxTbXrsCkEw4KheCT/KGg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -4662,9 +3906,9 @@ } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "emojis-list": { @@ -4688,35 +3932,47 @@ } }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" } }, "enhanced-resolve": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", - "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz", + "integrity": "sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==", "dev": true, "requires": { "graceful-fs": "^4.1.2", - "memory-fs": "^0.4.0", + "memory-fs": "^0.5.0", "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } } }, "entities": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", - "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", "dev": true }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "requires": { "prr": "~1.0.1" @@ -4732,23 +3988,28 @@ } }, "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -4778,9 +4039,9 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "eslint": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.0.1.tgz", - "integrity": "sha512-DyQRaMmORQ+JsWShYsSg4OPTjY56u1nCjAmICrE8vLWqyLKxhFXOthwMj1SA8xwfrv0CofLNVnqbfyhwCkaO0w==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -4789,60 +4050,52 @@ "cross-spawn": "^6.0.5", "debug": "^4.0.1", "doctrine": "^3.0.0", - "eslint-scope": "^4.0.3", - "eslint-utils": "^1.3.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^6.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", - "glob-parent": "^3.1.0", - "globals": "^11.7.0", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.2.2", + "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.3.0", - "lodash": "^4.17.11", + "lodash": "^4.17.14", "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", - "semver": "^5.5.1", - "strip-ansi": "^4.0.0", - "strip-json-comments": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", "table": "^5.2.3", - "text-table": "^0.2.0" + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", "dev": true, "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "type-fest": "^0.8.1" } }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -4871,17 +4124,13 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "restore-cursor": "^3.1.0" } }, "debug": { @@ -4893,12 +4142,6 @@ "ms": "^2.1.1" } }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, "external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -4910,131 +4153,144 @@ "tmp": "^0.0.33" } }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } + "is-glob": "^4.0.1" } }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", "dev": true, "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "type-fest": "^0.8.1" } }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, "inquirer": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz", - "integrity": "sha512-scfHejeG/lVZSpvCXpsB4j/wQNPM5JC8kiElOI0OUTwmc1RTpXr4H32/HOlQHcZiYl2z2VElwuCVDRG8vFmbnA==", + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "mimic-fn": "^2.1.0" } }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true } } @@ -5057,13 +4313,13 @@ "dev": true }, "eslint-import-resolver-node": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz", - "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", + "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", "dev": true, "requires": { "debug": "^2.6.9", - "resolve": "^1.5.0" + "resolve": "^1.13.1" } }, "eslint-loader": { @@ -5077,43 +4333,15 @@ "object-assign": "^4.0.1", "object-hash": "^1.1.4", "rimraf": "^2.6.1" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - } } }, "eslint-module-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz", - "integrity": "sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", + "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", "dev": true, "requires": { - "debug": "^2.6.8", + "debug": "^2.6.9", "pkg-dir": "^2.0.0" }, "dependencies": { @@ -5160,12 +4388,6 @@ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -5184,22 +4406,23 @@ "dev": true }, "eslint-plugin-import": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.1.tgz", - "integrity": "sha512-YEESFKOcMIXJTosb5YaepqVhQHGMb8dxkgov560GqMDP/658U5vk6FeVSR7xXLeYkPc7xPYy+uAoiYE/bKMphA==", + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", "dev": true, "requires": { "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", "contains-path": "^0.1.0", "debug": "^2.6.9", "doctrine": "1.5.0", "eslint-import-resolver-node": "^0.3.2", - "eslint-module-utils": "^2.4.0", + "eslint-module-utils": "^2.4.1", "has": "^1.0.3", "minimatch": "^3.0.4", "object.values": "^1.1.0", "read-pkg-up": "^2.0.0", - "resolve": "^1.11.0" + "resolve": "^1.12.0" }, "dependencies": { "doctrine": { @@ -5211,110 +4434,13 @@ "esutils": "^2.0.2", "isarray": "^1.0.0" } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } } } }, "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", "dev": true, "requires": { "esrecurse": "^4.1.0", @@ -5322,41 +4448,41 @@ } }, "eslint-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.0.tgz", - "integrity": "sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", "dev": true }, "espree": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.0.0.tgz", - "integrity": "sha512-lJvCS6YbCn3ImT3yKkPe0+tJ+mH6ljhGNjHQH9mRtiO6gjhVAOhVXW1yjnwqGwTkK3bGbye+hb00nFNmu0l/1Q==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", "dev": true, "requires": { - "acorn": "^6.0.7", - "acorn-jsx": "^5.0.0", - "eslint-visitor-keys": "^1.0.0" + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" } }, "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha1-QGxRZYsfWZGl+bYrHcJbAOPlxwg=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", "dev": true, "requires": { "estraverse": "^4.0.0" @@ -5365,22 +4491,22 @@ "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha1-AHo7n9vCs7uH5IeeoZyS/b05Qs8=", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { "estraverse": "^4.1.0" } }, "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "etag": { @@ -5400,15 +4526,15 @@ "integrity": "sha1-GMYgXRcKsJ24if/OqjPw5JPxSlA=" }, "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", "dev": true }, "events": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", - "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", + "integrity": "sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg==", "dev": true }, "eventsource": { @@ -5446,12 +4572,12 @@ } }, "execall": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execall/-/execall-1.0.0.tgz", - "integrity": "sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", "dev": true, "requires": { - "clone-regexp": "^1.0.0" + "clone-regexp": "^2.1.0" } }, "expand-brackets": { @@ -5489,48 +4615,6 @@ } } }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - }, - "dependencies": { - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -5622,7 +4706,7 @@ "external-editor": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha1-BFURz9jRM/OEZnPRBHwVTiFK09U=", + "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "requires": { "chardet": "^0.4.0", "iconv-lite": "^0.4.17", @@ -5691,12 +4775,6 @@ "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -5707,9 +4785,9 @@ "dev": true }, "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", "dev": true }, "fast-glob": { @@ -5727,9 +4805,9 @@ } }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { @@ -5744,6 +4822,15 @@ "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", "dev": true }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -5798,25 +4885,13 @@ } }, "file-loader": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.1.0.tgz", - "integrity": "sha512-ajDk1nlByoalZAGR4b0H6oD+EGlWnyW1qbSxzaUc7RFiqmn+RbXQQRbTc72jsiUIlVusJ4Et58ltds8ZwTfnAw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-4.3.0.tgz", + "integrity": "sha512-aKrYPYjF1yG3oX0kWRrqrSMfgftm7oJW5M+m4owoldH5C51C0RkIwB++JbRvEW3IU6/ZG5n8UvEcdgwOt2UOWA==", "dev": true, "requires": { "loader-utils": "^1.2.3", - "schema-utils": "^2.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz", - "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } + "schema-utils": "^2.5.0" } }, "file-type": { @@ -5825,11 +4900,12 @@ "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==", "dev": true }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "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==", + "dev": true, + "optional": true }, "fill-range": { "version": "4.0.0", @@ -5870,24 +4946,23 @@ } }, "find-cache-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", - "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", "dev": true, "requires": { "commondir": "^1.0.1", - "mkdirp": "^0.5.1", - "pkg-dir": "^1.0.0" + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" } }, "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "dev": true, "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" + "locate-path": "^3.0.0" } }, "findup-sync": { @@ -5911,6 +4986,31 @@ "flatted": "^2.0.0", "rimraf": "2.6.3", "write": "1.0.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "flatted": { @@ -5938,12 +5038,12 @@ } }, "follow-redirects": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.7.0.tgz", - "integrity": "sha512-m/pZQy4Gj287eNy94nivy5wchN3Kp+Q5WgUPNy5lJSZ3sgkVKSYV/ZChMAQVIgx1SqfZ2zBZtPA2YlXIWxxJOQ==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.10.0.tgz", + "integrity": "sha512-4eyLK6s6lH32nOvLLwlIOnr9zrL8Sm+OvW4pVTJNoXeGzYIkHVf+pADQi+OJ0E67hiuSLezPVPyBcIZO50TmmQ==", "dev": true, "requires": { - "debug": "^3.2.6" + "debug": "^3.0.0" }, "dependencies": { "debug": { @@ -5974,15 +5074,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -5994,7 +5085,6 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, - "optional": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -6032,10 +5122,19 @@ "readable-stream": "^2.0.0" } }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, "fs-readdir-recursive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha1-4y/AMKLM7kSmtTcTCNpUvgs5fSc=", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs-write-stream-atomic": { @@ -6056,14 +5155,15 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", - "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.11.tgz", + "integrity": "sha512-+ux3lx6peh0BpvY0JebGyZoiR4D+oYzdPZMKJwkZ+sFkNJzpL7tXc/wehS49gUAxg3tmMHPHZkA8JU2rhhgDHw==", "dev": true, "optional": true, "requires": { + "bindings": "^1.5.0", "nan": "^2.12.1", - "node-pre-gyp": "^0.12.0" + "node-pre-gyp": "*" }, "dependencies": { "abbrev": { @@ -6075,7 +5175,8 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -6096,19 +5197,21 @@ "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" } }, "chownr": { - "version": "1.1.1", + "version": "1.1.3", "bundled": true, "dev": true, "optional": true @@ -6116,17 +5219,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", @@ -6135,7 +5241,7 @@ "optional": true }, "debug": { - "version": "4.1.1", + "version": "3.2.6", "bundled": true, "dev": true, "optional": true, @@ -6162,12 +5268,12 @@ "optional": true }, "fs-minipass": { - "version": "1.2.5", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.6.0" } }, "fs.realpath": { @@ -6193,7 +5299,7 @@ } }, "glob": { - "version": "7.1.3", + "version": "7.1.6", "bundled": true, "dev": true, "optional": true, @@ -6222,7 +5328,7 @@ } }, "ignore-walk": { - "version": "3.0.1", + "version": "3.0.3", "bundled": true, "dev": true, "optional": true, @@ -6241,9 +5347,10 @@ } }, "inherits": { - "version": "2.0.3", + "version": "2.0.4", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -6255,6 +5362,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6269,6 +5377,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6276,53 +5385,56 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "minipass": { - "version": "2.3.5", + "version": "2.9.0", "bundled": true, "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.2.1", + "version": "1.3.3", "bundled": true, "dev": true, "optional": true, "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mkdirp": { "version": "0.5.1", "bundled": true, "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } }, "ms": { - "version": "2.1.1", + "version": "2.1.2", "bundled": true, "dev": true, "optional": true }, "needle": { - "version": "2.3.0", + "version": "2.4.0", "bundled": true, "dev": true, "optional": true, "requires": { - "debug": "^4.1.0", + "debug": "^3.2.6", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.12.0", + "version": "0.14.0", "bundled": true, "dev": true, "optional": true, @@ -6336,7 +5448,7 @@ "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^4" + "tar": "^4.4.2" } }, "nopt": { @@ -6350,13 +5462,22 @@ } }, "npm-bundled": { - "version": "1.0.6", + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true }, "npm-packlist": { - "version": "1.4.1", + "version": "1.4.7", "bundled": true, "dev": true, "optional": true, @@ -6380,7 +5501,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -6392,6 +5514,7 @@ "version": "1.4.0", "bundled": true, "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -6425,7 +5548,7 @@ "optional": true }, "process-nextick-args": { - "version": "2.0.0", + "version": "2.0.1", "bundled": true, "dev": true, "optional": true @@ -6466,7 +5589,7 @@ } }, "rimraf": { - "version": "2.6.3", + "version": "2.7.1", "bundled": true, "dev": true, "optional": true, @@ -6477,7 +5600,8 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -6492,7 +5616,7 @@ "optional": true }, "semver": { - "version": "5.7.0", + "version": "5.7.1", "bundled": true, "dev": true, "optional": true @@ -6513,6 +5637,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", @@ -6532,6 +5657,7 @@ "version": "3.0.1", "bundled": true, "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6543,18 +5669,18 @@ "optional": true }, "tar": { - "version": "4.4.8", + "version": "4.4.13", "bundled": true, "dev": true, "optional": true, "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" + "yallist": "^3.0.3" } }, "util-deprecate": { @@ -6575,12 +5701,14 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "yallist": { - "version": "3.0.3", + "version": "3.1.1", "bundled": true, - "dev": true + "dev": true, + "optional": true } } }, @@ -6599,7 +5727,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, "functional-red-black-tree": { @@ -6655,6 +5783,12 @@ "globule": "^1.0.0" } }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", @@ -6689,14 +5823,6 @@ "dev": true, "requires": { "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "glob": { @@ -6712,42 +5838,6 @@ "path-is-absolute": "^1.0.0" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -6792,57 +5882,63 @@ "dev": true, "requires": { "global-prefix": "^3.0.0" - }, - "dependencies": { - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } } }, "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" } }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "globby": { - "version": "6.1.0", - "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" }, "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true } } @@ -6854,20 +5950,20 @@ "dev": true }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz", + "integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==", "dev": true, "requires": { "glob": "~7.1.1", - "lodash": "~4.17.10", + "lodash": "~4.17.12", "minimatch": "~3.0.2" }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -6906,9 +6002,9 @@ } }, "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "handle-thing": { @@ -6921,20 +6017,24 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "optional": true + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, - "optional": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6959,9 +6059,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "has-unicode": { @@ -6979,14 +6079,6 @@ "get-value": "^2.0.6", "has-values": "^1.0.0", "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "has-values": { @@ -6999,26 +6091,6 @@ "kind-of": "^4.0.0" }, "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", @@ -7082,9 +6154,9 @@ } }, "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, "hpack.js": { @@ -7163,12 +6235,12 @@ "dev": true }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "dev": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" } } @@ -7181,40 +6253,12 @@ "dev": true, "requires": { "loader-utils": "^1.1.0" - }, - "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - } } }, "html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", "dev": true }, "html-webpack-plugin": { @@ -7232,6 +6276,12 @@ "util.promisify": "1.0.0" }, "dependencies": { + "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==", + "dev": true + }, "commander": { "version": "2.17.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", @@ -7253,6 +6303,12 @@ "uglify-js": "3.4.x" } }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "loader-utils": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", @@ -7281,10 +6337,16 @@ "readable-stream": "^3.1.1" }, "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -7328,12 +6390,12 @@ "dev": true }, "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", + "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", + "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } @@ -7348,392 +6410,100 @@ "is-glob": "^4.0.0", "lodash": "^4.17.11", "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "hyphenate-style-name": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", + "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "dev": true + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", + "dev": true, + "optional": true + }, + "imagemin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-6.1.0.tgz", + "integrity": "sha512-8ryJBL1CN5uSHpiBMX0rJw79C9F9aJqMnjGnrd/1CafegpNuA81RBAAru/jQQEOWlOJJlpRnlcVFF6wq+Ist0A==", + "dev": true, + "requires": { + "file-type": "^10.7.0", + "globby": "^8.0.1", + "make-dir": "^1.0.0", + "p-pipe": "^1.1.0", + "pify": "^4.0.1", + "replace-ext": "^1.0.0" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "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==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "hyphenate-style-name": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz", - "integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ==" - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "dev": true, - "requires": { - "postcss": "^7.0.14" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", - "dev": true - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "image-size": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", - "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=", - "dev": true, - "optional": true - }, - "imagemin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/imagemin/-/imagemin-6.1.0.tgz", - "integrity": "sha512-8ryJBL1CN5uSHpiBMX0rJw79C9F9aJqMnjGnrd/1CafegpNuA81RBAAru/jQQEOWlOJJlpRnlcVFF6wq+Ist0A==", - "dev": true, - "requires": { - "file-type": "^10.7.0", - "globby": "^8.0.1", - "make-dir": "^1.0.0", - "p-pipe": "^1.1.0", - "pify": "^4.0.1", - "replace-ext": "^1.0.0" - }, - "dependencies": { - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -7767,12 +6537,6 @@ } } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "dev": true - }, "make-dir": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", @@ -7790,22 +6554,11 @@ } } }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "dev": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "dev": true - } - } + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true } } }, @@ -7818,6 +6571,11 @@ "loader-utils": "^1.1.0" } }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" + }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -7828,30 +6586,13 @@ } }, "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "dependencies": { - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "dev": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - } + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" } }, "import-from": { @@ -7871,6 +6612,12 @@ } } }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -7879,66 +6626,6 @@ "requires": { "pkg-dir": "^3.0.0", "resolve-cwd": "^2.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - } } }, "imurmurhash": { @@ -7954,13 +6641,10 @@ "dev": true }, "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true }, "indexes-of": { "version": "1.0.1", @@ -7968,6 +6652,12 @@ "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", "dev": true }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7985,7 +6675,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha1-7uJfVtscnsYIXgwid4CD9Zar+Sc=" + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inline-style-prefixer": { "version": "2.0.5", @@ -8035,7 +6725,7 @@ "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha1-YQ88ksk1nOHbYW5TgAjSP/NRWOY=", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { "loose-envify": "^1.0.0" @@ -8053,12 +6743,24 @@ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "ipaddr.js": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", "dev": true }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", @@ -8066,12 +6768,23 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-alphabetical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.3.tgz", - "integrity": "sha512-eEMa6MKpHFzw38eKm56iNNi6GJ7lf6aLLio7Kr23sJPAECscgRtZvOBYybejWDQ2bM949Y++61PY+udzj5QMLA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", "dev": true }, "is-alphanumeric": { @@ -8081,15 +6794,21 @@ "dev": true }, "is-alphanumerical": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.3.tgz", - "integrity": "sha512-A1IGAPO5AW9vSh7omxIlOGwIqEvpW/TA+DksVOPM5ODuxKlZS09+TEM1E3275lJqO2oJ38vDpeAL3DCIiHE6eA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", "dev": true, "requires": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", + "dev": true + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -8108,13 +6827,13 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "dev": true }, "is-data-descriptor": { @@ -8124,24 +6843,35 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", "dev": true }, "is-decimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.3.tgz", - "integrity": "sha512-bvLSwoDg2q6Gf+E2LEPiklHZxxiSi3XAh4Mav65mKqTfCO1HM3uBs24TjEH8iJX3bbDdLXKJXBTmGzuTUuAEjQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", "dev": true }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -8152,7 +6882,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -8163,21 +6893,6 @@ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", "dev": true }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -8191,13 +6906,10 @@ "dev": true }, "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", + "dev": true }, "is-fullwidth-code-point": { "version": "2.0.0", @@ -8214,9 +6926,9 @@ } }, "is-hexadecimal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.3.tgz", - "integrity": "sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", "dev": true }, "is-number": { @@ -8226,12 +6938,23 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true }, "is-path-cwd": { @@ -8267,50 +6990,30 @@ "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha1-LBY7P6+xtgbZ0Xko8FwqHDjgdnc=", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" }, "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", "dev": true, "requires": { - "has": "^1.0.1" + "has": "^1.0.3" } }, "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", "dev": true }, "is-stream": { @@ -8318,19 +7021,19 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, - "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": "sha1-Ie4WUY0sHdPt0+mg1X5QIHrDZMo=", + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", "dev": true }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-typedarray": { @@ -8346,21 +7049,21 @@ "dev": true }, "is-whitespace-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.3.tgz", - "integrity": "sha512-SNPgMLz9JzPccD3nPctcj8sZlX9DAMJSKH8bP7Z6bohCwuNgX8xbWr1eTAYXX9Vpi/aSn8Y1akL9WgM3t43YNQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", "dev": true }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "is-word-character": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.3.tgz", - "integrity": "sha512-0wfcrFgOOOBdgRNT9H33xe6Zi6yhX/uoc4U8NBZGeQQB0ctU1dnlNTyL9JM2646bHDTpsDm1Brb3VPoCIMrd/A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", "dev": true }, "is-wsl": { @@ -8372,8 +7075,7 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", @@ -8424,15 +7126,15 @@ } }, "js-base64": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", - "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.2.tgz", + "integrity": "sha512-Vg8czh0Q7sFBSUMWWArX/miJeBWYBPpdU/3M/DKSaekLMqrqVPaedp+5mZhie/r0lgrcaYBfwXatEew6gwgiQQ==", "dev": true }, "js-beautify": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.0.tgz", - "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.3.tgz", + "integrity": "sha512-wfk/IAWobz1TfApSdivH5PJ0miIHgDoYb1ugSqHcODPmaYu46rYe5FVuIEkhjg8IQiv6rDNPyhsqbsohI/C2vQ==", "requires": { "config-chain": "^1.1.12", "editorconfig": "^0.15.3", @@ -8442,9 +7144,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -8465,12 +7167,6 @@ } } }, - "js-levenshtein": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", - "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8484,14 +7180,6 @@ "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - } } }, "jsbn": { @@ -8509,7 +7197,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": "sha1-u4Z8+zRQ5pEHwTHRxRS6s9yLyqk=", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, "json-schema": { @@ -8526,7 +7214,7 @@ "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha1-afaofZUTq4u4/mO9sJecRI5oRmA=", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, "json-stable-stringify-without-jsonify": { @@ -8548,10 +7236,13 @@ "dev": true }, "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } }, "jsonminify": { "version": "0.4.1", @@ -8569,20 +7260,12 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "jstree": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.8.tgz", - "integrity": "sha512-0/nhGxVLSGfGQyVg+q59ocqSEKWRDKHoA8wNrcOIvlzCCw19tzvcMNGJ19hf+U0b7fycABowkny7fQPcLgUwwA==", + "version": "3.3.9", + "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.9.tgz", + "integrity": "sha512-jRIbhg+BHrIs1Wm6oiJt3oKTVBE6sWS0PCp2/RlkIUqsLUPWUYgV3q8LfKoi1/E+YMzGtP6BuK4okk+0mwfmhQ==", "requires": { "jquery": ">=1.9.1" } @@ -8595,6 +7278,17 @@ "jquery": ">=1.9.1" } }, + "jszip": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.2.2.tgz", + "integrity": "sha512-NmKajvAFQpbg3taXQXr/ccS2wcucR1AZ+NtyWp2Nq7HHVsXhcJFR8p0Baf32C2yVvBylFWVeKf+WI2AnvlPhpA==", + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", @@ -8607,18 +7301,15 @@ "dev": true }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true }, "known-css-properties": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.5.0.tgz", - "integrity": "sha512-LOS0CoS8zcZnB1EjLw4LLqDXw8nvt3AGH5dXLQP3D9O1nLLA+9GC5GnPl5mmF+JiQAtSX4VyZC7KvEtcA4kUtA==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.17.0.tgz", + "integrity": "sha512-Vi3nxDGMm/z+lAaCjvAR1u+7fiv+sG6gU/iYDj5QOF8h76ytK9EW/EKfF0NeTyiGBi8Jy6Hklty/vxISrLox3w==", "dev": true }, "lcid": { @@ -8631,9 +7322,9 @@ } }, "leaflet": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.5.1.tgz", - "integrity": "sha512-ekM9KAeG99tYisNBg0IzEywAlp0hYI5XRipsqRXyRTeuU8jcuntilpp+eFf5gaE0xubc9RuSNIVtByEKwqFV0w==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.6.0.tgz", + "integrity": "sha512-CPkhyqWUKZKFJ6K8umN5/D2wrJ2+/8UIpXppY7QDnUZW5bZL5+SEI2J7GBpwh4LIupOKqbNSQXgqmrEJopHVNQ==" }, "leaflet-polylinedecorator": { "version": "1.6.0", @@ -8644,9 +7335,9 @@ } }, "leaflet-providers": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.8.0.tgz", - "integrity": "sha512-y0qr1PxrcCq3Vah+COptp29xDmuAEu4Wg/a8YDL+hztfqdsO+OQJzE4aZ+ZVoHFucX5HP5ELw0nrD+xa5T8m0g==" + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/leaflet-providers/-/leaflet-providers-1.9.1.tgz", + "integrity": "sha512-YpJB9y4/nT5NGicU9vuqlttJaCer6paD3J3b8Wrw+IIQvK9dtcdzE9CsTkDg7Dg9FeGp5NEr3hu17xcHbYI/2w==" }, "leaflet-rotatedmarker": { "version": "0.2.0", @@ -8659,9 +7350,9 @@ "integrity": "sha512-ZSEpE/EFApR0bJ1w/dUGwTSUvWlpalKqIzkaYdYB7jaftQA/Y2Jav+eT4CMtEYFj+ZK4mswP13Q2acnPBnhGOw==" }, "less": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/less/-/less-3.9.0.tgz", - "integrity": "sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w==", + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/less/-/less-3.11.1.tgz", + "integrity": "sha512-tlWX341RECuTOvoDIvtFqXsKj072hm3+9ymRBe76/mD6O5ZZecnlAOVDlWAleF2+aohFrxNidXhv2773f6kY7g==", "dev": true, "requires": { "clone": "^2.1.2", @@ -8672,7 +7363,8 @@ "mkdirp": "^0.5.0", "promise": "^7.1.1", "request": "^2.83.0", - "source-map": "~0.6.0" + "source-map": "~0.6.0", + "tslib": "^1.10.0" }, "dependencies": { "clone": { @@ -8709,6 +7401,21 @@ } } }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "dev": true, + "requires": { + "leven": "^3.1.0" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -8719,33 +7426,37 @@ "type-check": "~0.3.2" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "requires": { + "immediate": "~3.0.5" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" + "strip-bom": "^3.0.0" }, "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } } } }, @@ -8757,6 +7468,47 @@ "requires": { "find-cache-dir": "^0.1.1", "mkdirp": "0.5.1" + }, + "dependencies": { + "find-cache-dir": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-0.1.1.tgz", + "integrity": "sha1-yN765XyKUqinhPnjHFfHQumToLk=", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", + "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + } + } } }, "loader-runner": { @@ -8776,12 +7528,6 @@ "json5": "^1.0.1" }, "dependencies": { - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, "json5": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", @@ -8794,78 +7540,66 @@ } }, "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "dev": true, "requires": { - "p-locate": "^4.1.0" + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=" + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=", + "dev": true }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.merge": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=" + "lodash.isregexp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isregexp/-/lodash.isregexp-4.0.1.tgz", + "integrity": "sha1-4T5kezDNVZdSoEzZEghvr32hwws=", + "dev": true }, - "lodash.tail": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz", - "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=", + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha1-V0Dhxdbw39pK2TI7UzIQfva0xAo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { - "chalk": "^2.0.1" + "chalk": "^2.4.2" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -8885,7 +7619,7 @@ "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -8894,15 +7628,15 @@ } }, "loglevel": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", - "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==", + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", "dev": true }, "longest-streak": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.3.tgz", - "integrity": "sha512-9lz5IVdpwsKLMzQi0MQ+oD9EA0mIGcWYP7jXMTZVXP8D42PwuAk+M/HBFYQoxt1G5OR8m7aSIgb1UymfWGBWEw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", "dev": true }, "loose-envify": { @@ -8939,20 +7673,13 @@ } }, "make-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz", - "integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", - "dev": true - } + "pify": "^4.0.1", + "semver": "^5.6.0" } }, "make-plural": { @@ -9000,9 +7727,9 @@ } }, "markdown-escapes": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.3.tgz", - "integrity": "sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", "dev": true }, "markdown-table": { @@ -9049,16 +7776,10 @@ "prop-types": "^15.5.10" } }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, "mathml-tag-names": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz", - "integrity": "sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", "dev": true }, "md-color-picker": { @@ -9097,9 +7818,9 @@ "from": "git://github.com/alenaksu/mdPickers.git#0.7.5" }, "mdast-util-compact": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz", - "integrity": "sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" @@ -9156,6 +7877,87 @@ "read-pkg-up": "^1.0.1", "redent": "^1.0.0", "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + } } }, "merge-descriptors": { @@ -9165,9 +7967,9 @@ "dev": true }, "merge2": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.3.tgz", - "integrity": "sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", + "integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", "dev": true }, "messageformat": { @@ -9185,7 +7987,7 @@ "messageformat-parser": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-1.1.0.tgz", - "integrity": "sha1-E7oiUKdrvejg/KDbs0dflcWUqQo=" + "integrity": "sha512-Hwem6G3MsKDLS1FtBRGIs8T50P1Q00r3srS6QJePCFbad9fq0nYxwf3rnU2BreApRGhmpKMV7oZI06Sy1c9TPA==" }, "methods": { "version": "1.1.2", @@ -9212,14 +8014,6 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } } }, "miller-rabin": { @@ -9235,28 +8029,28 @@ "mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha1-Ms2eXGRVO9WNGaVor0Uqz/BJgbE=", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", "dev": true }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", "dev": true, "requires": { - "mime-db": "1.40.0" + "mime-db": "1.43.0" } }, "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha1-ggyGo5M0ZA6ZUWkovQP8qIBX0CI=" + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, "min-document": { "version": "2.19.0", @@ -9267,16 +8061,35 @@ "dom-walk": "^0.1.0" } }, + "min-indent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", + "integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", + "dev": true + }, "mini-css-extract-plugin": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.0.tgz", - "integrity": "sha512-MNpRGbNA52q6U92i0qbVpQNsgk7LExy41MdAlG84FeytfDOtRIf/mCHdEgG8rpTKOaNKiqUnZdlptF469hxqOw==", + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.8.2.tgz", + "integrity": "sha512-a3Y4of27Wz+mqK3qrcd3VhYz6cU0iW5x3Sgvqzbj+XmlrSizmvu8QQMl5oMYJjgHOC4iyt+w7l4umP+dQeW3bw==", "dev": true, "requires": { "loader-utils": "^1.1.0", "normalize-url": "1.9.1", "schema-utils": "^1.0.0", "webpack-sources": "^1.1.0" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "minimalistic-assert": { @@ -9294,7 +8107,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } @@ -9305,15 +8118,59 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha1-+6TIGRM54T7PTWG+sD8HAQPz2VQ=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.0.2.tgz", + "integrity": "sha512-seq4hpWkYSUh1y7NXxzucwAN9yVlBc3Upgdjz8vLCP97jG8kaOmzYrVH/m7tQ1NYD1wdtZbSLfdy4zFmRWuc/w==", "dev": true, "requires": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0" } }, + "minipass": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.1.tgz", + "integrity": "sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.2.tgz", + "integrity": "sha512-3JS5A2DKhD2g0Gg8x3yamO0pj7YeKGwVlDS90pF++kxptwx/F+B//roxf9SqYil5tQo65bijy+dAuAFZmYOouA==", + "dev": true, + "requires": { + "minipass": "^3.0.0" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -9345,7 +8202,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -9353,24 +8210,6 @@ } } }, - "mixin-object": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", - "dev": true, - "requires": { - "for-in": "^0.1.3", - "is-extendable": "^0.1.1" - }, - "dependencies": { - "for-in": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", - "dev": true - } - } - }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -9441,7 +8280,7 @@ "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha1-uHqKpPwN6P5r6IiVs4mD/yZb0Rk=", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9455,26 +8294,6 @@ "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true - } } }, "natural-compare": { @@ -9542,12 +8361,24 @@ "source-map": "0.5.6" }, "dependencies": { + "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==", + "dev": true + }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "loader-utils": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", @@ -9574,19 +8405,24 @@ } }, "ng-annotate-patched": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/ng-annotate-patched/-/ng-annotate-patched-1.10.0.tgz", - "integrity": "sha512-R0mcergG/aYSVF0sag7uFN2Mn+E9RZc3nfU+uB/aYmySreeya33Tx5/u2PuKjHt8Q8Kg+6duZVyJzrEtJHawEA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/ng-annotate-patched/-/ng-annotate-patched-1.11.1.tgz", + "integrity": "sha512-DmReqLu/cAdnXt7d0NpLC1hEDUH2z1CGs5ymQCjHd5+eAvWfkTl0k17pdFc0/C/EZRx+oed+4DJ7TsRILQVLUQ==", "dev": true, "requires": { - "acorn": "^6.0.5", - "acorn-dynamic-import": "^4.0.0", - "acorn-walk": "^6.1.1", + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "commander": "^3.0.1", "convert-source-map": "^1.1.2", - "optimist": "^0.6.1", "source-map": "^0.6.1" }, "dependencies": { + "commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9596,7 +8432,7 @@ } }, "ngFlowchart": { - "version": "git://github.com/thingsboard/ngFlowchart.git#1343a7478961f68280d81f0ecda4e722a2068e0f", + "version": "git://github.com/thingsboard/ngFlowchart.git#b941e4ed38c226019890b7b0802b71c2b147f0e0", "from": "git://github.com/thingsboard/ngFlowchart.git#master" }, "ngclipboard": { @@ -9656,7 +8492,7 @@ "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", - "integrity": "sha1-YLgTOWvjmz8SiKTB7V0efSi0ZKw=", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { "lower-case": "^1.1.1" @@ -9675,16 +8511,16 @@ "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", - "integrity": "sha1-mA9vcthSEaU0fGsrwYxbhMPrR+8=", + "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { "encoding": "^0.1.11", "is-stream": "^1.0.1" } }, "node-forge": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", - "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz", + "integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==", "dev": true }, "node-gyp": { @@ -9707,132 +8543,11 @@ "which": "1" }, "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "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 - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "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" - } } } }, @@ -9865,6 +8580,14 @@ "url": "^0.11.0", "util": "^0.11.0", "vm-browserify": "^1.0.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + } } }, "node-modules-regexp": { @@ -9874,18 +8597,26 @@ "dev": true }, "node-releases": { - "version": "1.1.25", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.25.tgz", - "integrity": "sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==", + "version": "1.1.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.49.tgz", + "integrity": "sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==", "dev": true, "requires": { - "semver": "^5.3.0" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "node-sass": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.12.0.tgz", - "integrity": "sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", + "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -9895,7 +8626,7 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash": "^4.17.11", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", "nan": "^2.13.2", @@ -9907,30 +8638,6 @@ "true-case-path": "^1.0.2" }, "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", @@ -9940,103 +8647,6 @@ "lru-cache": "^4.0.1", "which": "^1.2.9" } - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "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 - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "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" - } } } }, @@ -10102,7 +8712,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -10136,8 +8746,7 @@ "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -10163,6 +8772,15 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } } } }, @@ -10172,6 +8790,18 @@ "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", "dev": true }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.2.tgz", + "integrity": "sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ==", + "dev": true + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -10185,14 +8815,6 @@ "dev": true, "requires": { "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "object.assign": { @@ -10208,23 +8830,13 @@ } }, "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", "dev": true, "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" } }, "object.pick": { @@ -10234,24 +8846,16 @@ "dev": true, "requires": { "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "object.values": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz", - "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.12.0", + "es-abstract": "^1.17.0-next.1", "function-bind": "^1.1.1", "has": "^1.0.3" } @@ -10351,27 +8955,21 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true } } }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "options": { @@ -10431,23 +9029,12 @@ "osenv": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha1-hc36+uso6Gd/QW4odZK18/SepBA=", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "requires": { "os-homedir": "^1.0.0", "os-tmpdir": "^1.0.0" } }, - "output-file-sync": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", - "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "is-plain-obj": "^1.1.0", - "mkdirp": "^0.5.1" - } - }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -10467,28 +9054,31 @@ "dev": true }, "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", "dev": true, "requires": { "p-try": "^2.0.0" } }, "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "dev": true, "requires": { - "p-limit": "^2.2.0" + "p-limit": "^2.0.0" } }, "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } }, "p-pipe": { "version": "1.2.0", @@ -10512,18 +9102,17 @@ "dev": true }, "pako": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.10.tgz", - "integrity": "sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw==", - "dev": true + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", "dev": true, "requires": { - "cyclist": "~0.2.2", + "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" } @@ -10547,9 +9136,9 @@ } }, "parse-asn1": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", - "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", "dev": true, "requires": { "asn1.js": "^4.0.0", @@ -10574,35 +9163,6 @@ "is-hexadecimal": "^1.0.0" } }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - }, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - } - } - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", @@ -10643,13 +9203,10 @@ "dev": true }, "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -10681,20 +9238,18 @@ "dev": true }, "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { - "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -10717,6 +9272,12 @@ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -10746,23 +9307,49 @@ } }, "pkg-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-1.0.0.tgz", - "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", "dev": true, "requires": { - "find-up": "^1.0.0" + "find-up": "^3.0.0" } }, "portfinder": { - "version": "1.0.21", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.21.tgz", - "integrity": "sha512-ESabpDCzmBS3ekHbmpAIiESq3udRsCBGiBZLsC+HgBKv2ezb0R4oG+7RnYEVZ/ZCfhel5Tx3UzdNWA0Lox2QCA==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" + }, + "dependencies": { + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } } }, "posix-character-classes": { @@ -10772,9 +9359,9 @@ "dev": true }, "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "version": "7.0.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.27.tgz", + "integrity": "sha512-WuQETPMcW9Uf1/22HWUWP9lgsIC+KEHg2kozMflKjbeUtw9ujvFX6QmIfozaErDkmLWS9WEnEdEe6Uo9/BNTdQ==", "dev": true, "requires": { "chalk": "^2.4.2", @@ -10831,52 +9418,30 @@ } }, "postcss-html": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.12.0.tgz", - "integrity": "sha512-KxKUpj7AY7nlCbLcTOYxdfJnGE7QFAfU2n95ADj1Q90RM/pOLdz8k3n4avOyRFs7MDQHcRzJQWM1dehCwJxisQ==", + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", + "dev": true, + "requires": { + "htmlparser2": "^3.10.0" + } + }, + "postcss-jsx": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.4.tgz", + "integrity": "sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==", "dev": true, "requires": { - "htmlparser2": "^3.9.2", - "remark": "^8.0.0", - "unist-util-find-all-after": "^1.0.1" + "@babel/core": ">=7.2.2" } }, "postcss-less": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-1.1.5.tgz", - "integrity": "sha512-QQIiIqgEjNnquc0d4b6HDOSFZxbFQoy4MPpli2lSLpKhMyBkKwwca2HFqu4xzxlKID/F2fxSOowwtKpgczhF7A==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", "dev": true, "requires": { - "postcss": "^5.2.16" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } + "postcss": "^7.0.14" } }, "postcss-load-config": { @@ -10892,7 +9457,7 @@ "postcss-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha1-a5eUPkfHLYRfqeA/Jzdz1OjdbC0=", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -10901,97 +9466,29 @@ "schema-utils": "^1.0.0" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "dev": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", - "dev": true, - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", - "dev": true - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "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==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } } } }, + "postcss-markdown": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", + "dev": true, + "requires": { + "remark": "^10.0.1", + "unist-util-find-all-after": "^1.0.2" + } + }, "postcss-media-query-parser": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", @@ -11020,9 +9517,9 @@ } }, "postcss-modules-scope": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.0.tgz", - "integrity": "sha512-91Rjps0JnmtUB0cujlc8KIKCsJXWjzuxGeT/+Q2i2HXKZ7nBUeF9YQTZZTNvHVoNYj1AthsjnGLtqDUE0Op79A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.1.1.tgz", + "integrity": "sha512-OXRUPecnHCg8b9xWvldG/jUpRIGPNRka0r4D4j0ESUU2/5IOnpsjfPPmDprM3Ih8CgZ8FXjWqaniK5v4rWt3oQ==", "dev": true, "requires": { "postcss": "^7.0.6", @@ -11040,21 +9537,21 @@ } }, "postcss-reporter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-5.0.0.tgz", - "integrity": "sha512-rBkDbaHAu5uywbCR2XE8a25tats3xSOsGNx6mppK6Q9kSFGKc/FyAzfci+fWM2l+K402p1D0pNcfDGxeje5IKg==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", "dev": true, "requires": { - "chalk": "^2.0.1", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "postcss": "^6.0.8" + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" }, "dependencies": { "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -11071,27 +9568,19 @@ "supports-color": "^5.3.0" } }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", "dev": true, "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" + "chalk": "^2.0.1" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -11106,175 +9595,31 @@ "dev": true }, "postcss-safe-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-3.0.1.tgz", - "integrity": "sha1-t1Pv9sfArqXoN1++TN6L+QY/8UI=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", "dev": true, "requires": { - "postcss": "^6.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.26" } }, "postcss-sass": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.2.0.tgz", - "integrity": "sha512-cUmYzkP747fPCQE6d+CH2l1L4VSyIlAzZsok3HPjb5Gzsq3jE+VjpAdGlPsnQ310WKWI42sw+ar0UNN59/f3hg==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.2.tgz", + "integrity": "sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ==", "dev": true, "requires": { - "gonzales-pe": "^4.0.3", - "postcss": "^6.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "gonzales-pe": "^4.2.4", + "postcss": "^7.0.21" } }, "postcss-scss": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-1.0.6.tgz", - "integrity": "sha512-4EFYGHcEw+H3E06PT/pQQri06u/1VIIPjeJQaM8skB80vZuXMhp4cSNV5azmdNkontnOID/XYWEvEEELLFB1ww==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", + "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", "dev": true, "requires": { - "postcss": "^6.0.23" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.0" } }, "postcss-selector-parser": { @@ -11296,20 +9641,18 @@ "requires": { "lodash": "^4.17.14", "postcss": "^7.0.17" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } }, + "postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true + }, "postcss-value-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz", - "integrity": "sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", + "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==", "dev": true }, "prelude-ls": { @@ -11324,12 +9667,6 @@ "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", "dev": true }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", @@ -11341,9 +9678,9 @@ } }, "prismjs": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.16.0.tgz", - "integrity": "sha512-OA4MKxjFZHSvZcisLGe14THYsug/nF6O1f0pAJc0KN0wTyAcLqmsbE+lTGKSpyh+9pEW57+k6pg2AfYR+coyHA==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz", + "integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==", "requires": { "clipboard": "^2.0.0" }, @@ -11364,7 +9701,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha1-I4Hts2ifelPWUxkAYPz4ItLzaP8=", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", "dev": true }, "process": { @@ -11376,8 +9713,7 @@ "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "progress": { "version": "2.0.3", @@ -11388,7 +9724,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "requires": { "asap": "~2.0.3" } @@ -11436,9 +9772,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.1.33", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.33.tgz", - "integrity": "sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", + "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", "dev": true }, "public-encrypt": { @@ -11468,7 +9804,7 @@ "pumpify": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha1-NlE74karJ1cLGjdKXOJ4v9dDcM4=", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", "dev": true, "requires": { "duplexify": "^3.6.0", @@ -11489,17 +9825,16 @@ } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true, - "optional": true + "dev": true }, "query-string": { "version": "4.3.4", @@ -11530,9 +9865,9 @@ "dev": true }, "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", "dev": true }, "raf": { @@ -11543,37 +9878,6 @@ "performance-now": "^2.1.0" } }, - "ramda": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", - "integrity": "sha1-j99oIxz/qQvC+UYDkKDLdKKbKak=", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11600,9 +9904,9 @@ "dev": true }, "raphael": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/raphael/-/raphael-2.2.8.tgz", - "integrity": "sha512-0kWKcGn4lXTw4eUiOhjspYiG+v0m6zSmTmlO62E0hl2CYKUvCuHER9YKqXYvOn2nj24mYp8jzHOLeBuj/Gn28Q==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/raphael/-/raphael-2.3.0.tgz", + "integrity": "sha512-w2yIenZAQnp257XUWGni4bLMVxpUpcIl7qgxEgDIXtmSypYtlNxfXWpOBxs7LBTps5sDwhRnrToJrMUrivqNTQ==", "requires": { "eve-raphael": "0.5.0" } @@ -11635,18 +9939,6 @@ "requires": { "loader-utils": "^1.1.0", "schema-utils": "^2.0.1" - }, - "dependencies": { - "schema-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz", - "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } } }, "rc-align": { @@ -11661,22 +9953,23 @@ } }, "rc-animate": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.8.3.tgz", - "integrity": "sha512-VPSHJF/PW9zrPVCdQ94/YOI2lFfJVlaiAeQveJN2nlPVMivgvXkuFJyfe42GbZqm+qlnRjH9B4WbY9rCZz9miw==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.10.2.tgz", + "integrity": "sha512-cE/A7piAzoWFSgUD69NmmMraqCeqVBa51UErod8NS3LUEqWfppSVagHfa0qHAlwPVPiIBg3emRONyny3eiH0Dg==", "requires": { "babel-runtime": "6.x", "classnames": "^2.2.6", "css-animation": "^1.3.2", "prop-types": "15.x", "raf": "^3.4.0", + "rc-util": "^4.15.3", "react-lifecycles-compat": "^3.0.4" } }, "rc-menu": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-5.1.4.tgz", - "integrity": "sha1-5d8I/ouDPoFGkTX/E7MKuPIf88Y=", + "integrity": "sha512-ZUkUNda70GtTXcQDiO3rSDdk3sgIwDwzPUm5dVM8nRH/j84qv0BVBkIUwIBu8+s+G3G9lWLurRqh22dCqZPeOA==", "requires": { "babel-runtime": "6.x", "classnames": "2.x", @@ -11707,7 +10000,7 @@ "rc-trigger": { "version": "1.11.5", "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-1.11.5.tgz", - "integrity": "sha1-+I+fhODnn44O8cjRv4rCIItxViA=", + "integrity": "sha512-MBuUPw1nFzA4K7jQOwb7uvFaZFjXGd00EofUYiZ+l/fgKVq8wnLC0lkv36kwqM7vfKyftRo2sh7cWVpdPuNnnw==", "requires": { "babel-runtime": "6.x", "create-react-class": "15.x", @@ -11718,14 +10011,15 @@ } }, "rc-util": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.6.0.tgz", - "integrity": "sha512-rbgrzm1/i8mgfwOI4t1CwWK7wGe+OwX+dNa7PVMgxZYPBADGh86eD4OcJO1UKGeajIMDUUKMluaZxvgraQIOmw==", + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.19.0.tgz", + "integrity": "sha512-mptALlLwpeczS3nrv83DbwJNeupolbuvlIEjcvimSiWI8NUBjpF0HgG3kWp1RymiuiRCNm9yhaXqDz0a99dpgQ==", "requires": { "add-dom-event-listener": "^1.1.0", "babel-runtime": "6.x", "prop-types": "^15.5.10", - "shallowequal": "^0.2.2" + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" } }, "react": { @@ -11801,9 +10095,9 @@ } }, "react-hot-loader": { - "version": "4.12.8", - "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.8.tgz", - "integrity": "sha512-/Df2J3znMHzRzI6CW0dTOIWD2sjkVHxv56XCqujAo9mR+k2PVTiGjUgYBiGPGsix9zQzgCRfOKca93o9Zdj2vQ==", + "version": "4.12.19", + "resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.19.tgz", + "integrity": "sha512-p8AnA4QE2GtrvkdmqnKrEiijtVlqdTIDCHZOwItkI9kW51bt5XnQ/4Anz8giiWf9kqBpEQwsmnChDCAFBRyR/Q==", "dev": true, "requires": { "fast-levenshtein": "^2.0.6", @@ -11812,25 +10106,19 @@ "loader-utils": "^1.1.0", "prop-types": "^15.6.1", "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.0.2", + "shallowequal": "^1.1.0", "source-map": "^0.7.3" }, "dependencies": { "hoist-non-react-statics": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz", - "integrity": "sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", "dev": true, "requires": { "react-is": "^16.7.0" } }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "dev": true - }, "source-map": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", @@ -11840,9 +10128,9 @@ } }, "react-is": { - "version": "16.8.6", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", - "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==" + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==" }, "react-lifecycles-compat": { "version": "3.0.4", @@ -11871,7 +10159,7 @@ "react-transition-group": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha1-4R9yslf5IbITIpp3TfRmEjRsfKY=", + "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", "requires": { "chain-function": "^1.0.0", "dom-helpers": "^3.2.0", @@ -11883,37 +10171,98 @@ "reactcss": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", - "integrity": "sha1-wAATh15Vexzw39mjaKHD2rO1SN0=", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", "requires": { "lodash": "^4.0.1" } }, "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", "dev": true, "requires": { - "load-json-file": "^1.0.0", + "load-json-file": "^2.0.0", "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" + "path-type": "^2.0.0" + }, + "dependencies": { + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } } }, "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", "dev": true, "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha1-sRwn2IuP8fvgcGQ8+UsMea4bCq8=", - "dev": true, + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -11933,296 +10282,28 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + } + }, + "recast": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", + "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", + "dev": true, + "requires": { + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "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==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } } } }, - "recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "dev": true, - "requires": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - } - }, "recompose": { "version": "0.21.2", "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.21.2.tgz", @@ -12242,6 +10323,17 @@ "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + } } }, "regenerate": { @@ -12262,32 +10354,36 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha1-vgWtf5v30i4Fb5cmzuUBf78Z4uk=" + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "regenerator-transform": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", + "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", "dev": true, "requires": { - "is-equal-shallow": "^0.1.3" + "private": "^0.1.6" } }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" } }, - "regexp-tree": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.11.tgz", - "integrity": "sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==", - "dev": true + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } }, "regexpp": { "version": "2.0.1", @@ -12295,6 +10391,43 @@ "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", "dev": true }, + "regexpu-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", + "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.1.0", + "regjsgen": "^0.5.0", + "regjsparser": "^0.6.0", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.1.0" + } + }, + "regjsgen": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", + "dev": true + }, + "regjsparser": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz", + "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -12302,20 +10435,20 @@ "dev": true }, "remark": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/remark/-/remark-8.0.0.tgz", - "integrity": "sha512-K0PTsaZvJlXTl9DN6qYlvjTkqSZBFELhROZMrblm2rB+085flN84nz4g/BscKRMqDvhzlK1oQ/xnWQumdeNZYw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", "dev": true, "requires": { - "remark-parse": "^4.0.0", - "remark-stringify": "^4.0.0", - "unified": "^6.0.0" + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" } }, "remark-parse": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-4.0.0.tgz", - "integrity": "sha512-XZgICP2gJ1MHU7+vQaRM+VA9HEL3X253uwUM/BGgx3iv6TH2B3bF3B8q00DKcyP9YrJV+/7WOWEWBFF/u8cIsw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", "dev": true, "requires": { "collapse-white-space": "^1.0.2", @@ -12324,7 +10457,7 @@ "is-whitespace-character": "^1.0.0", "is-word-character": "^1.0.0", "markdown-escapes": "^1.0.0", - "parse-entities": "^1.0.2", + "parse-entities": "^1.1.0", "repeat-string": "^1.5.4", "state-toggle": "^1.0.0", "trim": "0.0.1", @@ -12336,9 +10469,9 @@ } }, "remark-stringify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-4.0.0.tgz", - "integrity": "sha512-xLuyKTnuQer3ke9hkU38SUYLiTmS078QOnoFavztmbt/pAJtNSkNtFgR0U//uCcmG0qnyxao+PDuatQav46F1w==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", "dev": true, "requires": { "ccount": "^1.0.0", @@ -12404,11 +10537,10 @@ "dev": true }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "dev": true, - "optional": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -12417,7 +10549,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -12427,7 +10559,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" } @@ -12438,12 +10570,6 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha1-iaf92TgmEmcxjq/hT5wy5ZjDaQk=", - "dev": true - }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", @@ -12462,9 +10588,9 @@ "integrity": "sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=" }, "resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", - "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -12507,6 +10633,19 @@ "is-windows": "^1.0.1", "resolve-dir": "^1.0.0" } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } } } }, @@ -12534,7 +10673,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, "retry": { @@ -12543,19 +10682,25 @@ "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { "glob": "^7.1.3" }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -12586,6 +10731,12 @@ "is-promise": "^2.1.0" } }, + "run-parallel": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", + "integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", + "dev": true + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -12601,9 +10752,9 @@ "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=" }, "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -12612,8 +10763,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -12627,7 +10777,7 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha1-RPoWGwGHuVSd2Eu5GAL5vYOFzWo=" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass-graph": { "version": "2.2.4", @@ -12642,23 +10792,22 @@ } }, "sass-loader": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.1.0.tgz", - "integrity": "sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-7.3.1.tgz", + "integrity": "sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==", "dev": true, "requires": { - "clone-deep": "^2.0.1", + "clone-deep": "^4.0.1", "loader-utils": "^1.0.1", - "lodash.tail": "^4.1.1", "neo-async": "^2.5.0", - "pify": "^3.0.0", - "semver": "^5.5.0" + "pify": "^4.0.1", + "semver": "^6.3.0" }, "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true } } @@ -12669,42 +10818,21 @@ "integrity": "sha1-uURTkjbJTi1OzjXS7j+iLxCy7UM=" }, "schema-inspector": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-1.6.8.tgz", - "integrity": "sha1-ueU5g8xV/y29e2Xj2+CF2dEoXyo=", + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-1.6.9.tgz", + "integrity": "sha512-MNS3SOn6noecIv9R+gwroIgiOLQoRY1IRXToFvVBo2QMfnXy1E+SGRVWJFsJPqgy0lAivUfPLaVLhvAI35HKRg==", "requires": { - "async": "^1.5.0" + "async": "^3.1.0" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha1-C3mpMgTXtgDUsoUNH2bCo0lRx3A=", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.4.tgz", + "integrity": "sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ==", "dev": true, "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "dependencies": { - "ajv": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", - "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", - "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", - "dev": true - } + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1" } }, "scss-tokenizer": { @@ -12740,18 +10868,18 @@ "dev": true }, "selfsigned": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.4.tgz", - "integrity": "sha512-9AukTiDmHXGXWtWjembZ5NDmVvP2695EtpgbCsxCa68w3c88B+alqbmZ4O3hZ4VWGXeGWzEVdvqgAJD8DQPCDw==", + "version": "1.10.7", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz", + "integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==", "dev": true, "requires": { - "node-forge": "0.7.5" + "node-forge": "0.9.0" } }, "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "send": { "version": "0.17.1", @@ -12783,9 +10911,9 @@ } }, "serialize-javascript": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.7.0.tgz", - "integrity": "sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", "dev": true }, "serve-index": { @@ -12847,6 +10975,11 @@ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -12892,31 +11025,18 @@ } }, "shallow-clone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz", - "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "requires": { - "is-extendable": "^0.1.1", - "kind-of": "^5.0.0", - "mixin-object": "^2.0.1" - }, - "dependencies": { - "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==", - "dev": true - } + "kind-of": "^6.0.2" } }, "shallowequal": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-0.2.2.tgz", - "integrity": "sha1-HjL9W8q2rWiKSBLLDMBO/HXHAU4=", - "requires": { - "lodash.keys": "^3.1.2" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" }, "shebang-command": { "version": "1.2.0", @@ -12961,9 +11081,9 @@ "dev": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", "dev": true }, "slice-ansi": { @@ -12991,7 +11111,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { "base": "^0.11.1", @@ -13027,7 +11147,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { "define-property": "^1.0.0", @@ -13047,7 +11167,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13056,7 +11176,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -13065,41 +11185,40 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true } } }, "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "sockjs": { "version": "0.3.19", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", - "integrity": "sha1-2Xa76ACve9IK4IWY1YI5NQiZPA0=", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", "dev": true, "requires": { "faye-websocket": "^0.10.0", @@ -13107,9 +11226,9 @@ } }, "sockjs-client": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", - "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", "dev": true, "requires": { "debug": "^3.2.5", @@ -13168,12 +11287,12 @@ "dev": true }, "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha1-cuLMNAlVQ+Q7LGKyxMENSpBU8lk=", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", "dev": true, "requires": { - "atob": "^2.1.1", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", @@ -13181,9 +11300,9 @@ } }, "source-map-support": { - "version": "0.5.12", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", - "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -13223,7 +11342,7 @@ "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha1-meEZt6XaAOBUkcn6M4t5BII7QdA=", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", "dev": true, "requires": { "spdx-exceptions": "^2.1.0", @@ -13231,15 +11350,15 @@ } }, "spdx-license-ids": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", - "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", "dev": true }, "spdy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.0.tgz", - "integrity": "sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", + "integrity": "sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA==", "dev": true, "requires": { "debug": "^4.1.0", @@ -13296,9 +11415,9 @@ "dev": true }, "readable-stream": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", - "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "requires": { "inherits": "^2.0.3", @@ -13309,15 +11428,15 @@ } }, "specificity": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.3.2.tgz", - "integrity": "sha512-Nc/QN/A425Qog7j9aHmwOrlwX2e7pNI47ciwxwy4jOlvbbMHkNNJchit+FX+UjF3IAdiaaV5BKeWuDUnws6G1A==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", "dev": true }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -13349,23 +11468,16 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1" + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" } }, "stable": { @@ -13375,9 +11487,9 @@ "dev": true }, "state-toggle": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", - "integrity": "sha512-8LpelPGR0qQM4PnfLiplOQNJcIN1/r2Gy0xKB2zKnIW2YzPMt2sR4I/+gtPjhN7Svh9kw+zqEg2SFwpBO9iNiw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", "dev": true }, "static-extend": { @@ -13450,9 +11562,9 @@ } }, "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, "strict-uri-encode": { @@ -13464,7 +11576,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -13485,11 +11597,30 @@ } } }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", - "dev": true, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" } @@ -13497,7 +11628,7 @@ "stringify-entities": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", - "integrity": "sha1-qYQX5Ucf0iez5F09sYYcEcr2aPc=", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", "dev": true, "requires": { "character-entities-html4": "^1.0.0", @@ -13548,9 +11679,9 @@ } }, "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=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", "dev": true }, "style-loader": { @@ -13561,6 +11692,19 @@ "requires": { "loader-utils": "^1.1.0", "schema-utils": "^1.0.0" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "style-search": { @@ -13570,322 +11714,292 @@ "dev": true }, "stylelint": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-8.4.0.tgz", - "integrity": "sha512-56hPH5mTFnk8LzlEuTWq0epa34fHuS54UFYQidBOFt563RJBNi1nz1F2HK2MoT1X1waq47milvRsRahFCCJs/Q==", + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.0.0.tgz", + "integrity": "sha512-6sjgOJbM3iLhnUtmRO0J1vvxie9VnhIZX/2fCehjylv9Gl9u0ytehGCTm9Lhw2p1F8yaNZn5UprvhCB8C3g/Tg==", "dev": true, "requires": { - "autoprefixer": "^7.1.2", + "autoprefixer": "^9.7.3", "balanced-match": "^1.0.0", - "chalk": "^2.0.1", - "cosmiconfig": "^3.1.0", - "debug": "^3.0.0", - "execall": "^1.0.0", - "file-entry-cache": "^2.0.0", - "get-stdin": "^5.0.1", - "globby": "^7.0.0", + "chalk": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "execall": "^2.0.0", + "file-entry-cache": "^5.0.1", + "get-stdin": "^7.0.0", + "global-modules": "^2.0.0", + "globby": "^11.0.0", "globjoin": "^0.1.4", - "html-tags": "^2.0.0", - "ignore": "^3.3.3", + "html-tags": "^3.1.0", + "ignore": "^5.1.4", + "import-lazy": "^4.0.0", "imurmurhash": "^0.1.4", - "known-css-properties": "^0.5.0", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "mathml-tag-names": "^2.0.1", - "meow": "^4.0.0", - "micromatch": "^2.3.11", + "known-css-properties": "^0.17.0", + "leven": "^3.1.0", + "lodash": "^4.17.15", + "log-symbols": "^3.0.0", + "mathml-tag-names": "^2.1.1", + "meow": "^6.0.0", + "micromatch": "^4.0.2", "normalize-selector": "^0.2.0", - "pify": "^3.0.0", - "postcss": "^6.0.6", - "postcss-html": "^0.12.0", - "postcss-less": "^1.1.0", + "postcss": "^7.0.26", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.3", + "postcss-less": "^3.1.4", + "postcss-markdown": "^0.36.0", "postcss-media-query-parser": "^0.2.3", - "postcss-reporter": "^5.0.0", + "postcss-reporter": "^6.0.1", "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^3.0.1", - "postcss-sass": "^0.2.0", - "postcss-scss": "^1.0.2", + "postcss-safe-parser": "^4.0.1", + "postcss-sass": "^0.4.2", + "postcss-scss": "^2.0.0", "postcss-selector-parser": "^3.1.0", - "postcss-value-parser": "^3.3.0", - "resolve-from": "^4.0.0", - "specificity": "^0.3.1", - "string-width": "^2.1.0", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.0.2", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", "style-search": "^0.1.0", - "sugarss": "^1.0.0", + "sugarss": "^2.0.0", "svg-tags": "^1.0.0", - "table": "^4.0.1" + "table": "^5.4.6", + "v8-compile-cache": "^2.1.0", + "write-file-atomic": "^3.0.1" }, "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } + "@nodelib/fs.stat": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz", + "integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==", + "dev": true }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "dev": true, "requires": { - "arr-flatten": "^1.0.1" + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" } }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "fill-range": "^7.0.1" } }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.1.2.tgz", + "integrity": "sha512-QfFrU0CIw2oltVvpndW32kuJ/9YOJwUnmWrjlXt1nnJZHCaS9i6bfOpg9R4Lw8aZjStkJWM+jc0cdXjWBgVJSw==", "dev": true, "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" } }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, - "cosmiconfig": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-3.1.0.tgz", - "integrity": "sha512-zedsBhLSbPBms+kE7AH4vHg6JsKDz6epSv2/+5XHs8ILHlgDciSJfSWf8sX9aQ52Jb7KI7VswUTsLpR/G0cr2Q==", + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^3.0.0", - "require-from-string": "^2.0.1" + "color-name": "~1.1.4" } }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", "dev": true, "requires": { - "ms": "^2.1.1" + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" } }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "is-posix-bracket": "^0.1.0" + "ms": "^2.1.1" } }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "requires": { - "is-extglob": "^1.0.0" + "path-type": "^4.0.0" } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "fast-glob": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.1.1.tgz", + "integrity": "sha512-nTCREpBY8w8r+boyFYAx21iL6faSsQynliPHM4Uf56SbkyohCNxpVPEH9xrF5TXKy+IsjkPUHDKiUkzBVRXn9g==", "dev": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "locate-path": "^2.0.0" + "to-regex-range": "^5.0.1" } }, - "flat-cache": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", - "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "graceful-fs": "^4.1.2", - "rimraf": "~2.6.2", - "write": "^0.2.1" + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" } }, "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", "dev": true }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "is-glob": "^4.0.1" } }, "globby": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", + "integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", "dev": true, "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", "dev": true }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "dependencies": { - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "dev": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } - } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "^4.1.0" } }, "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", "dev": true }, "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-6.0.1.tgz", + "integrity": "sha512-kxGTFgT/b7/oSRSQsJ0qsT5IMU+bgZ1eAdSA3kIV7onkW0QWo/hL5RbGlMfvBjHJKPE1LaPX0kdecYFiqYWjUw==", "dev": true, "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.1.1", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.0.0", + "minimist-options": "^4.0.1", + "normalize-package-data": "^2.5.0", + "read-pkg-up": "^7.0.0", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.8.1", + "yargs-parser": "^16.1.0" } }, "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, "ms": { @@ -13894,588 +12008,287 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "^2.2.0" } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, "parse-json": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-3.0.0.tgz", - "integrity": "sha1-+m9HsY4jgm6tMvJj50TQ4ehH+xM=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", "dev": true, "requires": { - "error-ex": "^1.3.1" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" } }, "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true }, "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha1-zvMdyOCho7sNEFwM2Xzzv0f0428=", - "dev": true, - "requires": { - "pify": "^3.0.0" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", "dev": true, "requires": { - "dot-prop": "^4.1.1", + "dot-prop": "^5.2.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" } }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", - "dev": true - }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "dev": true, "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } } }, "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", "dev": true, "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" } }, "redent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", "dev": true, "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" } }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha1-BE8aSdiEL/MHqta1Be0Xi9lQE00=", + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", - "dev": true + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } }, "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" } }, - "table": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.3.tgz", - "integrity": "sha512-S7rnFITmBH1EnyKcvxBh1LjYeQMmnZtCXSEbHcH6S0NoKit24ZuFO/T1vDcLdYsLQkM188PVVhQmzKIuThNkKg==", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "ajv": "^6.0.1", - "ajv-keywords": "^3.0.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "is-number": "^7.0.0" } }, "trim-newlines": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.0.tgz", + "integrity": "sha512-C4+gOpvmxaSMKuEf9Qc134F1ZuOHVXKRbtEflf4NTtuuJDEIJ9p5PXsalL8SkeRw+qit1Mo+yuvMPAKwWg/1hA==", "dev": true }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "yargs-parser": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", + "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", "dev": true, "requires": { - "mkdirp": "^0.5.1" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } }, "stylelint-config-recommended": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz", - "integrity": "sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", + "integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==", "dev": true }, "stylelint-config-recommended-scss": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-3.3.0.tgz", - "integrity": "sha512-BvuuLYwoet8JutOP7K1a8YaiENN+0HQn390eDi0SWe1h7Uhx6O3GUQ6Ubgie9b/AmHX4Btmp+ZzVGbzriFTBcA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-4.1.0.tgz", + "integrity": "sha512-4012ca0weVi92epm3RRBRZcRJIyl5vJjJ/tJAKng+Qat5+cnmuCwyOI2vXkKdjNfGd0gvzyKCKEkvTMDcbtd7Q==", "dev": true, "requires": { - "stylelint-config-recommended": "^2.2.0" + "stylelint-config-recommended": "^3.0.0" } }, "stylelint-config-standard": { - "version": "18.3.0", - "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-18.3.0.tgz", - "integrity": "sha512-Tdc/TFeddjjy64LvjPau9SsfVRexmTFqUhnMBrzz07J4p2dVQtmpncRF/o8yZn8ugA3Ut43E6o1GtjX80TFytw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-19.0.0.tgz", + "integrity": "sha512-VvcODsL1PryzpYteWZo2YaA5vU/pWfjqBpOvmeA8iB2MteZ/ZhI1O4hnrWMidsS4vmEJpKtjdhLdfGJmmZm6Cg==", "dev": true, "requires": { - "stylelint-config-recommended": "^2.2.0" + "stylelint-config-recommended": "^3.0.0" } }, "stylelint-order": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-3.0.1.tgz", - "integrity": "sha512-isVEJ1oUoVB7bb5pYop96KYOac4c+tLOqa5dPtAEwAwQUVSbi7OPFbfaCclcTjOlXicymasLpwhRirhFWh93yw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-4.0.0.tgz", + "integrity": "sha512-bXV0v+jfB0+JKsqIn3mLglg1Dj2QCYkFHNfL1c+rVMEmruZmW5LUqT/ARBERfBm8SFtCuXpEdatidw/3IkcoiA==", "dev": true, "requires": { - "lodash": "^4.17.14", - "postcss": "^7.0.17", + "lodash": "^4.17.15", + "postcss": "^7.0.26", "postcss-sorting": "^5.0.1" - }, - "dependencies": { - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - } } }, "stylelint-scss": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.9.2.tgz", - "integrity": "sha512-VUh173p3T1qJf016P7yeJ6nxkUpqF5qQ+VSDw3J8P6wEJbA1loaNgBHR3k3skHvUkF+9brLO1ibCHA00pjW3cw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-3.13.0.tgz", + "integrity": "sha512-SaLnvQyndaPcsgVJsMh6zJ1uKVzkRZJx+Wg/stzoB1mTBdEmGketbHrGbMQNymzH/0mJ06zDSpeCDvNxqIJE5A==", "dev": true, "requires": { - "lodash": "^4.17.11", + "lodash.isboolean": "^3.0.3", + "lodash.isregexp": "^4.0.1", + "lodash.isstring": "^4.0.1", "postcss-media-query-parser": "^0.2.3", "postcss-resolve-nested-selector": "^0.1.1", "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.0" + "postcss-value-parser": "^4.0.2" } }, "stylelint-webpack-plugin": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-0.10.5.tgz", - "integrity": "sha1-C24NNz/14DuqgZfr4PJiWYG9Jms=", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stylelint-webpack-plugin/-/stylelint-webpack-plugin-1.2.3.tgz", + "integrity": "sha512-XEevZZzlI6k3e0Amp7AtpZ/elgaOdPPwLFY9InNoajw4KNRcZTkK61ZsZdHvIyK32Ej9L9u4fwfXG2QGKW0imA==", "dev": true, "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "ramda": "^0.25.0" + "arrify": "^2.0.1", + "micromatch": "^4.0.2", + "schema-utils": "^2.6.1" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", "dev": true }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" } }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" + "to-regex-range": "^5.0.1" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "braces": "^3.0.1", + "picomatch": "^2.0.5" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha1-ARRrNqYhjmTljzqNZt5df8b20FE=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "is-number": "^7.0.0" } } } }, "sugarss": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-1.0.1.tgz", - "integrity": "sha512-3qgLZytikQQEVn1/FrhY7B68gPUUGY3R1Q1vTiD5xT+Ti1DP/8iZuwFet9ONs5+bmL8pZoDQ6JrQHVgrNlK6mA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", "dev": true, "requires": { - "postcss": "^6.0.14" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha1-QfuyAkPlCxK+DwS43tvwdSDOhB0=", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "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": "sha1-4uaaRKyHcveKHsCzW2id9lMO/I8=", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "postcss": "^7.0.2" } }, "supports-color": { @@ -14492,12 +12305,12 @@ "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha1-wiaIrtTqs83C3+rLtWFmBWCgCAQ=" + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "table": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.4.tgz", - "integrity": "sha512-IIfEAUx5QlODLblLrGTTLJA7Tk0iLSGBvgY8essPRVNGHAzThujww1YqHLs6h3HfTg55h++RzLHH5Xw/rfv+mg==", + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", "dev": true, "requires": { "ajv": "^6.10.2", @@ -14506,28 +12319,16 @@ "string-width": "^3.0.0" }, "dependencies": { - "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, "ansi-regex": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", "dev": true }, "string-width": { @@ -14570,9 +12371,9 @@ } }, "terser": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.1.2.tgz", - "integrity": "sha512-jvNoEQSPXJdssFwqPSgWjsOrb+ELoE+ILpHPKXC83tIxOlh2U75F1KuB2luLD/3a6/7K3Vw5pDn+hvu0C4AzSw==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.3.tgz", + "integrity": "sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -14589,91 +12390,98 @@ } }, "terser-webpack-plugin": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.3.0.tgz", - "integrity": "sha512-W2YWmxPjjkUcOWa4pBEv4OP4er1aeQJlSo2UhtCFQCuRXEHjOFscO8VyWHj9JLlA0RzQb8Y2/Ta78XZvT54uGg==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", + "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", "dev": true, "requires": { - "cacache": "^11.3.2", - "find-cache-dir": "^2.0.0", + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", - "loader-utils": "^1.2.3", "schema-utils": "^1.0.0", - "serialize-javascript": "^1.7.0", + "serialize-javascript": "^2.1.2", "source-map": "^0.6.1", - "terser": "^4.0.0", - "webpack-sources": "^1.3.0", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "yallist": "^3.0.2" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "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==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "find-up": "^3.0.0" + "figgy-pudding": "^3.5.1" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true } } @@ -14700,15 +12508,15 @@ } }, "thunky": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.3.tgz", - "integrity": "sha512-YwT8pjmNcAXBZqrubu22P4FYsh2D4dxRmnWBOL8Jk8bUcRUtc5326kx32tuTmFDAZtLOGEVNl8POAR8j896Iow==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, "timers-browserify": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", - "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", "dev": true, "requires": { "setimmediate": "^1.0.4" @@ -14727,7 +12535,7 @@ "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha1-bTQzWIl2jSGyvNoKonfO07G/rfk=", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "requires": { "os-tmpdir": "~1.0.2" } @@ -14738,6 +12546,12 @@ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -14745,12 +12559,23 @@ "dev": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { "define-property": "^2.0.2", @@ -14767,17 +12592,6 @@ "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } } }, "toidentifier": { @@ -14787,9 +12601,9 @@ "dev": true }, "tooltipster": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/tooltipster/-/tooltipster-4.2.6.tgz", - "integrity": "sha1-+/ej9bQL2D6BV04o2WZ8+CZnvHk=" + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/tooltipster/-/tooltipster-4.2.7.tgz", + "integrity": "sha512-W4tY3LG2eyPY2VQZRH3JcsNuRl3jPCEGmKBPOMTP/05E3+1kOJjASzPRRkcpP+uf9vqX7+896ivU86f6B8Esgw==" }, "toposort": { "version": "1.0.7", @@ -14798,14 +12612,13 @@ "dev": true }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, - "optional": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "trim": { @@ -14820,22 +12633,16 @@ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=", "dev": true }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, "trim-trailing-lines": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.2.tgz", - "integrity": "sha512-MUjYItdrqqj2zpcHFTkMa9WAv4JHTI6gnRQGPFLrt5L9a6tRMiDnIqYl8JBvu2d2Tc3lWJKQwlGCp0K8AvCM+Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", "dev": true }, "trough": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.4.tgz", - "integrity": "sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", "dev": true }, "true-case-path": { @@ -14848,9 +12655,9 @@ }, "dependencies": { "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -14886,7 +12693,6 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -14911,6 +12717,12 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -14927,15 +12739,24 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typeface-roboto": { "version": "0.0.22", "resolved": "https://registry.npmjs.org/typeface-roboto/-/typeface-roboto-0.0.22.tgz", "integrity": "sha1-A7YLsCsQ+VCaaDImsDmucEFj5WE=" }, "ua-parser-js": { - "version": "0.7.20", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz", - "integrity": "sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw==" + "version": "0.7.21", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.21.tgz", + "integrity": "sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==" }, "uglify-js": { "version": "3.4.10", @@ -14956,107 +12777,121 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM=", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } }, "uglifyjs-webpack-plugin": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.1.3.tgz", - "integrity": "sha512-/lRkCaFbI6pT3CxsQHDhBcqB6tocOnqba0vJqJ2DzSWFLRgOIiip8q0nVFydyXk+n8UtF7ZuS6hvWopcYH5FuA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-2.2.0.tgz", + "integrity": "sha512-mHSkufBmBuJ+KHQhv5H0MXijtsoA1lynJt1lXOaotja8/I0pR4L9oGaPIZw+bQBOFittXZg9OC1sXSGO9D9ZYg==", "dev": true, "requires": { - "cacache": "^11.3.2", + "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", "serialize-javascript": "^1.7.0", "source-map": "^0.6.1", - "uglify-js": "^3.5.12", - "webpack-sources": "^1.3.0", + "uglify-js": "^3.6.0", + "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "cacache": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.3.tgz", + "integrity": "sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw==", "dev": true, "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" + "yallist": "^3.0.2" } }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "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==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "serialize-javascript": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", + "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", "dev": true, "requires": { - "p-limit": "^2.0.0" + "figgy-pudding": "^3.5.1" } }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz", + "integrity": "sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ==", "dev": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -15066,13 +12901,13 @@ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=" }, "unherit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.2.tgz", - "integrity": "sha512-W3tMnpaMG7ZY6xe/moK04U9fBhi6wEiCYHUW5Mop/wQHf12+79EQGwxYejNdhEz2mkqkBlGwm7pxmgBKMVUj0w==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "xtend": "^4.0.1" + "inherits": "^2.0.0", + "xtend": "^4.0.0" } }, "unicode-canonical-property-names-ecmascript": { @@ -15104,16 +12939,18 @@ "dev": true }, "unified": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/unified/-/unified-6.2.0.tgz", - "integrity": "sha1-f71jD3GRJtZ9QMZEt+P2FwNfbbo=", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", "dev": true, "requires": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", "bail": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^1.1.0", "trough": "^1.0.0", - "vfile": "^2.0.0", + "vfile": "^3.0.0", "x-is-string": "^0.1.0" } }, @@ -15154,9 +12991,9 @@ } }, "unist-util-find-all-after": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.4.tgz", - "integrity": "sha512-CaxvMjTd+yF93BKLJvZnEfqdM7fgEACsIpQqz8vIj9CJnUb9VpyymFS3tg6TCtgrF7vfCJBF5jbT2Ox9CBRYRQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz", + "integrity": "sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==", "dev": true, "requires": { "unist-util-is": "^3.0.0" @@ -15169,19 +13006,22 @@ "dev": true }, "unist-util-remove-position": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", - "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" } }, "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": "sha1-Pzf881EnncvKdICrWIm7ioMu4cY=", - "dev": true + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.2.tgz", + "integrity": "sha512-nK5n8OGhZ7ZgUwoUbL8uiVRwAbZyzBsB/Ddrlbu6jwwubFza4oe15KlyEaLNMXQW1svOQq4xesUeqA85YrIUQA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } }, "unist-util-visit": { "version": "1.4.1", @@ -15244,19 +13084,13 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, "upath": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", - "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, "upper-case": { @@ -15268,18 +13102,10 @@ "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha1-lMVA4f93KVbiKZUHwBCupsiDjrA=", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", "dev": true, "requires": { "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=", - "dev": true - } } }, "urix": { @@ -15307,14 +13133,14 @@ } }, "url-loader": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.1.0.tgz", - "integrity": "sha512-kVrp/8VfEm5fUt+fl2E0FQyrpmOYgMEkBsv8+UDP1wFhszECq5JyGF33I7cajlVY90zRZ6MyfgKXngLvHYZX8A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-2.3.0.tgz", + "integrity": "sha512-goSdg8VY+7nPZKUEChZSEtW5gjbS66USIGCeSJ1OVOJ7Yfuh/36YxCwMi5HVEJh6mqUYOoy3NJ0vlOMrWsSHog==", "dev": true, "requires": { "loader-utils": "^1.2.3", "mime": "^2.4.4", - "schema-utils": "^2.0.0" + "schema-utils": "^2.5.0" }, "dependencies": { "mime": { @@ -15322,16 +13148,6 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", "dev": true - }, - "schema-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.0.1.tgz", - "integrity": "sha512-HJFKJ4JixDpRur06QHwi8uu2kZbng318ahWEKgBjc0ZklcE4FDvmm2wghb448q0IRaABxIESt8vqPFvwgMB80A==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } } } }, @@ -15348,7 +13164,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha1-1QyMrHmhn7wg8pEfVuuXP04QBw8=", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, "util": { @@ -15371,8 +13187,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -15397,9 +13212,9 @@ "dev": true }, "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", "dev": true }, "v-accordion": { @@ -15408,9 +13223,9 @@ "integrity": "sha1-8KiaFsLWlcEe4sq4uptRkevIthM=" }, "v8-compile-cache": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", - "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "dev": true }, "v8flags": { @@ -15447,47 +13262,63 @@ "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "vfile": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", - "integrity": "sha1-5i2OcrIOg8MkvGxnJ47ickiL+Eo=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", "dev": true, "requires": { - "is-buffer": "^1.1.4", + "is-buffer": "^2.0.0", "replace-ext": "1.0.0", "unist-util-stringify-position": "^1.0.0", "vfile-message": "^1.0.0" + }, + "dependencies": { + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "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==", + "dev": true + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + } } }, "vfile-location": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", - "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", "dev": true }, "vfile-message": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", - "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.2.tgz", + "integrity": "sha512-gNV2Y2fDvDOOqq8bEe7cF3DXU6QgV4uA9zMR2P8tix11l1r7zju3zry3wZ8sx+BEfuO6WQ7z2QzfWTvqHQiwsA==", "dev": true, "requires": { - "unist-util-stringify-position": "^1.1.1" + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" } }, "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, "w3c-blob": { @@ -15532,40 +13363,69 @@ } }, "webpack": { - "version": "4.37.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.37.0.tgz", - "integrity": "sha512-iJPPvL7XpbcbwOthbzpa2BSPlmGp8lGDokAj/LdWtK80rsPoPOdANSbDBf2GAVLKZD3GhCuQ/gGkgN9HWs0Keg==", + "version": "4.41.6", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.41.6.tgz", + "integrity": "sha512-yxXfV0Zv9WMGRD+QexkZzmGIh54bsvEs+9aRWxnN8erLWEOehAKUTeNBoUbA6HPEZPlRo7KDi2ZcNveoZgK9MA==", "dev": true, "requires": { "@webassemblyjs/ast": "1.8.5", "@webassemblyjs/helper-module-context": "1.8.5", "@webassemblyjs/wasm-edit": "1.8.5", "@webassemblyjs/wasm-parser": "1.8.5", - "acorn": "^6.2.0", - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0", - "chrome-trace-event": "^1.0.0", + "acorn": "^6.2.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^4.1.0", - "eslint-scope": "^4.0.0", + "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.3.0", - "loader-utils": "^1.1.0", - "memory-fs": "~0.4.1", - "micromatch": "^3.1.8", - "mkdirp": "~0.5.0", - "neo-async": "^2.5.0", - "node-libs-browser": "^2.0.0", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.1", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", "schema-utils": "^1.0.0", - "tapable": "^1.1.0", - "terser-webpack-plugin": "^1.1.0", - "watchpack": "^1.5.0", - "webpack-sources": "^1.3.0" + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.6.0", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "dev": true + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "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==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "webpack-cli": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.6.tgz", - "integrity": "sha512-0vEa83M7kJtxK/jUhlpZ27WHIOndz5mghWL2O53kiDoA9DIxSKnfqB92LoqEn77cT4f3H2cZm1BMEat/6AZz3A==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.11.tgz", + "integrity": "sha512-dXlfuml7xvAFwYUPsrtQAA9e4DOe58gnzSxhgrO/ZM/gyXTBowrsYeubyN4mqGhYdpXMFNyQ6emjJS9M7OBd4g==", "dev": true, "requires": { "chalk": "2.4.2", @@ -15596,12 +13456,6 @@ "color-convert": "^1.9.0" } }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -15635,13 +13489,21 @@ "wrap-ansi": "^5.1.0" } }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "enhanced-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz", + "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==", "dev": true, "requires": { - "locate-path": "^3.0.0" + "graceful-fs": "^4.1.2", + "memory-fs": "^0.4.0", + "tapable": "^1.0.0" } }, "get-caller-file": { @@ -15665,16 +13527,6 @@ "invert-kv": "^2.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", @@ -15686,21 +13538,6 @@ "mem": "^4.0.0" } }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -15736,6 +13573,12 @@ "has-flag": "^3.0.0" } }, + "v8-compile-cache": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz", + "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", + "dev": true + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", @@ -15749,554 +13592,144 @@ "dev": true, "requires": { "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - } - }, - "yargs": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", - "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", - "dev": true, - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.0" - } - }, - "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "webpack-dev-middleware": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", - "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.2", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - } - } - }, - "webpack-dev-server": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.7.2.tgz", - "integrity": "sha512-mjWtrKJW2T9SsjJ4/dxDC2fkFVUw8jlpemDERqV0ZJIkjjjamR2AbQlr3oz+j4JLhYCHImHnXZK5H06P2wvUew==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.6", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.2.1", - "http-proxy-middleware": "^0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "killable": "^1.0.1", - "loglevel": "^1.6.3", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.20", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.4", - "semver": "^6.1.1", - "serve-index": "^1.9.1", - "sockjs": "0.3.19", - "sockjs-client": "1.3.0", - "spdy": "^4.0.0", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.0", - "webpack-log": "^2.0.0", - "yargs": "12.0.5" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "chokidar": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.6.tgz", - "integrity": "sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } - } - }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "dev": true, - "requires": { - "ms": "^2.1.1" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "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" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "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==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "yargs": { + "version": "13.2.4", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", + "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.0" } }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } - }, - "is-number": { + } + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz", + "integrity": "sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.2.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.6", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.25", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.19", + "sockjs-client": "1.4.0", + "spdy": "^4.0.1", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "12.0.5" + }, + "dependencies": { + "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "kind-of": "^3.0.2" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" }, "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "ansi-regex": "^3.0.0" } } } }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, "lcid": { @@ -16308,57 +13741,10 @@ "invert-kv": "^2.0.0" } }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", - "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "opn": { @@ -16381,50 +13767,21 @@ "mem": "^4.0.0" } }, - "p-limit": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", - "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "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==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" } }, "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, "supports-color": { @@ -16436,24 +13793,21 @@ "has-flag": "^3.0.0" } }, - "webpack-dev-middleware": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz", - "integrity": "sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.2", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - } - }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", @@ -16518,9 +13872,9 @@ } }, "webpack-sources": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", - "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", "dev": true, "requires": { "source-list-map": "^2.0.0", @@ -16549,7 +13903,7 @@ "websocket-extensions": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "whatwg-fetch": { @@ -16581,10 +13935,16 @@ "string-width": "^1.0.2 || 2" } }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "worker-farm": { @@ -16642,10 +14002,22 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.5.tgz", - "integrity": "sha1-y9nm514J/F0skAFfIfDECHXg3VE=", + "integrity": "sha512-o3KqipXNUdS7wpQzBHSe180lBGO60SoK0yVo3CYJgb2MkobuWuBX6dhkYP5ORCLd55y+SaflMOV5fqAB53ux4w==", "requires": { "options": ">=0.0.5", "ultron": "1.0.x" @@ -16674,6 +14046,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, + "yaml": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.3" + } + }, "yargs": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", @@ -16701,6 +14082,16 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, "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", @@ -16710,6 +14101,66 @@ "number-is-nan": "^1.0.0" } }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -16721,6 +14172,15 @@ "strip-ansi": "^3.0.0" } }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 6e4591cd4d..0a6b06d498 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -1177,8 +1177,19 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device let newEntity = { name: entityParameters.name, type: entityParameters.type, - label: entityParameters.label + label: entityParameters.label, + additionalInfo: { + description: entityParameters.description + } }; + + if (entityType === types.entityType.device && entityParameters.gateway !== null) { + newEntity.additionalInfo = { + ...newEntity.additionalInfo, + gateway: entityParameters.gateway + }; + } + let saveEntityPromise = getEntitySavePromise(entityType, newEntity, config); saveEntityPromise.then(function success(response) { diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index e78af1dff7..57ac8825f5 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -397,6 +397,14 @@ export default angular.module('thingsboard.types', []) accessToken: { name: 'import.column-type.access-token', value: 'ACCESS_TOKEN' + }, + isGateway: { + name: 'import.column-type.isgateway', + value: 'gateway' + }, + description: { + name: 'import.column-type.description', + value: 'description' } }, aliasEntityType: { 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 1e7a89b95c..4ae2a7a9f0 100644 --- a/ui/src/app/import-export/import-dialog-csv.controller.js +++ b/ui/src/app/import-export/import-dialog-csv.controller.js @@ -122,10 +122,13 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo ignoreErrors: true, resendRequest: true }; + for (var i = 0; i < importData.rows.length; i++) { var entityData = { name: "", type: "", + description: "", + gateway: null, label: "", accessToken: "", attributes: { @@ -166,6 +169,12 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo case types.importEntityColumnType.label.value: entityData.label = importData.rows[i][j]; break; + case types.importEntityColumnType.isGateway.value: + entityData.gateway = importData.rows[i][j]; + break; + case types.importEntityColumnType.description.value: + entityData.description = 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 7520d99cdd..a295621799 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; + vm.columnTypes.description = types.importEntityColumnType.description; switch (vm.entityType) { case types.entityType.device: @@ -51,6 +52,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) { vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute; vm.columnTypes.timeseries = types.importEntityColumnType.timeseries; vm.columnTypes.accessToken = types.importEntityColumnType.accessToken; + vm.columnTypes.gateway = types.importEntityColumnType.isGateway; break; case types.entityType.asset: vm.columnTypes.serverAttribute = types.importEntityColumnType.serverAttribute; @@ -58,12 +60,23 @@ function TableColumnsAssignmentController($scope, types, $timeout) { break; } + $scope.isColumnTypeDiffers = function(columnType) { + return columnType !== types.importEntityColumnType.name.value && + columnType !== types.importEntityColumnType.type.value && + columnType !== types.importEntityColumnType.label.value && + columnType !== types.importEntityColumnType.accessToken.value&& + columnType !== types.importEntityColumnType.isGateway.value&& + columnType !== types.importEntityColumnType.description.value; + }; + $scope.$watch('vm.columns', function(newVal){ if (newVal) { var isSelectName = false; var isSelectType = false; var isSelectLabel = false; var isSelectCredentials = false; + var isSelectGateway = false; + var isSelectDescription = false; for (var i = 0; i < newVal.length; i++) { switch (newVal[i].type) { case types.importEntityColumnType.name.value: @@ -78,9 +91,14 @@ function TableColumnsAssignmentController($scope, types, $timeout) { case types.importEntityColumnType.accessToken.value: isSelectCredentials = true; break; + case types.importEntityColumnType.isGateway.value: + isSelectGateway = true; + break; + case types.importEntityColumnType.description.value: + isSelectDescription = true; } } - if(isSelectName && isSelectType) { + if (isSelectName && isSelectType) { vm.theForm.$setDirty(); } else { vm.theForm.$setPristine(); @@ -89,6 +107,8 @@ function TableColumnsAssignmentController($scope, types, $timeout) { vm.columnTypes.name.disable = isSelectName; vm.columnTypes.type.disable = isSelectType; vm.columnTypes.label.disable = isSelectLabel; + vm.columnTypes.gateway.disable = isSelectGateway; + vm.columnTypes.description.disable = isSelectDescription; 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 09491ec5e8..023155d6ac 100644 --- a/ui/src/app/import-export/table-columns-assignment.tpl.html +++ b/ui/src/app/import-export/table-columns-assignment.tpl.html @@ -39,10 +39,7 @@ + ng-if="isColumnTypeDiffers(column.type)"> Date: Wed, 19 Feb 2020 15:24:02 +0200 Subject: [PATCH 208/261] UI: (#2372) Added fetchLastLevelOnly checkbox to alias query filter. Updated ENG, RUS, UKR locales. Updated getRelatedEntities and (constructRelatedEntitiesSearchQuery) function(s) by adding fetchLastLevelOnly attribute. --- ui/src/app/api/entity.service.js | 15 +++++---- ui/src/app/entity/entity-filter.directive.js | 1 + ui/src/app/entity/entity-filter.tpl.html | 32 ++++++++++++++++++++ ui/src/app/locale/locale.constant-en_US.json | 1 + ui/src/app/locale/locale.constant-ru_RU.json | 1 + ui/src/app/locale/locale.constant-uk_UA.json | 1 + 6 files changed, 45 insertions(+), 6 deletions(-) diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js index 0a6b06d498..b795432dee 100644 --- a/ui/src/app/api/entity.service.js +++ b/ui/src/app/api/entity.service.js @@ -593,7 +593,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device parameters: { rootId: relationQueryRootEntityId.id, rootType: relationQueryRootEntityId.entityType, - direction: filter.direction + direction: filter.direction, + fetchLastLevelOnly: filter.fetchLastLevelOnly }, filters: filter.filters }; @@ -643,7 +644,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device parameters: { rootId: searchQueryRootEntityId.id, rootType: searchQueryRootEntityId.entityType, - direction: filter.direction + direction: filter.direction, + fetchLastLevelOnly: filter.fetchLastLevelOnly }, relationType: filter.relationType }; @@ -1075,10 +1077,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } } - function getRelatedEntities(rootEntityId, entityType, entitySubTypes, maxLevel, keys, typeTranslatePrefix, relationType, direction) { + function getRelatedEntities(rootEntityId, entityType, entitySubTypes, maxLevel, keys, typeTranslatePrefix, relationType, direction, fetchLastLevelOnly) { var deferred = $q.defer(); - var entitySearchQuery = constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel, relationType, direction); + var entitySearchQuery = constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel, relationType, direction,fetchLastLevelOnly); if (!entitySearchQuery) { deferred.reject(); } else { @@ -1499,13 +1501,14 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device ); } - function constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel, relationType, direction) { + function constructRelatedEntitiesSearchQuery(rootEntityId, entityType, entitySubTypes, maxLevel, relationType, direction, fetchLastLevelOnly) { var searchQuery = { parameters: { rootId: rootEntityId.id, rootType: rootEntityId.entityType, - direction: direction + direction: direction, + fetchLastLevelOnly: !!fetchLastLevelOnly }, relationType: relationType }; diff --git a/ui/src/app/entity/entity-filter.directive.js b/ui/src/app/entity/entity-filter.directive.js index e49d79d228..39e3892ff9 100644 --- a/ui/src/app/entity/entity-filter.directive.js +++ b/ui/src/app/entity/entity-filter.directive.js @@ -83,6 +83,7 @@ export default function EntityFilterDirective($compile, $templateCache, $q, $doc filter.rootEntity = null; filter.direction = types.entitySearchDirection.from; filter.maxLevel = 1; + filter.fetchLastLevelOnly = false; if (filter.type === types.aliasFilterType.relationsQuery.value) { filter.filters = []; } else if (filter.type === types.aliasFilterType.assetSearchQuery.value) { diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html index eb889bd215..bfc63a7d24 100644 --- a/ui/src/app/entity/entity-filter.tpl.html +++ b/ui/src/app/entity/entity-filter.tpl.html @@ -161,6 +161,14 @@ +
+
+ + + +
+
@@ -222,6 +230,14 @@
+
+
+ + + +
+
@@ -291,6 +307,14 @@
+
+
+ + + +
+
@@ -360,6 +384,14 @@
+
+
+ + + +
+
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index ba49ffb7fb..3527b82e06 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -207,6 +207,7 @@ "entity-filter-no-entity-matched": "No entities matching specified filter were found.", "no-entity-filter-specified": "No entity filter specified", "root-state-entity": "Use dashboard state entity as root", + "last-level-relation": "Fetch last level relation only", "root-entity": "Root entity", "state-entity-parameter-name": "State entity parameter name", "default-state-entity": "Default state entity", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index f4ed6d5c93..8d8ab8fa1f 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -206,6 +206,7 @@ "entity-filter-no-entity-matched": "Объекты, соответствующие фильтру, не найдены.", "no-entity-filter-specified": "Не указан фильтр объектов", "root-state-entity": "Использовать объект, полученный из дашборда, как корневой", + "last-level-relation": "Использовать только отношения последнего уровня", "root-entity": "Корневой объект", "state-entity-parameter-name": "Название объекта состояния", "default-state-entity": "Объект состояния по умолчанию", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 53d16bea5e..a0cdfa1841 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -225,6 +225,7 @@ "entity-filter-no-entity-matched": "Не знайдено жодних сутностей, які відповідають вказаному фільтру.", "no-entity-filter-specified": "Фільтр обїектів не вказано", "root-state-entity": "Використовувати сутінсть стану як кореневу", + "last-level-relation": "Використовувати лише відношення останнього рівня", "group-state-entity": "Використовувати групу сутностей стану як кореневу", "root-entity": "Коренева сутність", "state-entity-parameter-name": "Параметр сутності стану", From 64e0c42ab76b05db9047e4556b7ee3739e845c8c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 19 Feb 2020 15:39:22 +0200 Subject: [PATCH 209/261] Rest Client improvements --- .../java/org/thingsboard/client/tools/RestClient.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 1b5c4e5435..8b230b1238 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -1317,8 +1317,8 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); } - public void deleteEntityView(String entityViewId) { - restTemplate.delete(baseURL + "/api/entityView/{entityViewId}", entityViewId); + public void deleteEntityView(EntityViewId entityViewId) { + restTemplate.delete(baseURL + "/api/entityView/{entityViewId}", entityViewId.getId()); } public Optional getTenantEntityView(String entityViewName) { @@ -1445,14 +1445,14 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); } - public JsonNode handleTwoWayDeviceRPCRequest(String deviceId, JsonNode requestBody) { + public JsonNode handleTwoWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { return restTemplate.exchange( baseURL + "/api/plugins/rpc/twoway/{deviceId}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference() { }, - deviceId).getBody(); + deviceId.getId()).getBody(); } public Optional getRuleChainById(RuleChainId ruleChainId) { @@ -1917,7 +1917,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public Optional getWidgetTypeById(WidgetsBundleId widgetTypeId) { + public Optional getWidgetTypeById(WidgetTypeId widgetTypeId) { try { ResponseEntity widgetType = restTemplate.getForEntity(baseURL + "/api/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId.getId()); From 1e5ee5beb9d29674a694569717f96441f9d36b69 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 15:47:45 +0200 Subject: [PATCH 210/261] Fix conflicts --- .../server/service/install/SqlDatabaseUpgradeService.java | 4 ++++ dao/src/main/resources/sql/schema-entities.sql | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 aa5d3e3d95..d5c01801a0 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 @@ -214,6 +214,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;"); } catch (Exception e) { } + try { + conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration SET DATA TYPE varchar(100000000);"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + } catch (Exception e) { + } log.info("Schema updated."); } break; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 55893fc124..8d28329047 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -108,7 +108,7 @@ CREATE TABLE IF NOT EXISTS customer ( CREATE TABLE IF NOT EXISTS dashboard ( id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, - configuration varchar(10000000), + configuration varchar(100000000), assigned_customers varchar(1000000), search_text varchar(255), tenant_id varchar(31), From 2ea3b0bdafac7470752c87c96d415a9c7fdb7706 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 19 Feb 2020 15:50:19 +0200 Subject: [PATCH 211/261] Add service methods to claim a device (#2391) --- ui/src/app/api/device.service.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js index 1d3e481567..b803ec333a 100644 --- a/ui/src/app/api/device.service.js +++ b/ui/src/app/api/device.service.js @@ -43,7 +43,9 @@ function DeviceService($http, $q, $window, userService, attributeService, custom sendTwoWayRpcCommand: sendTwoWayRpcCommand, findByQuery: findByQuery, getDeviceTypes: getDeviceTypes, - findByName: findByName + findByName: findByName, + claimDevice: claimDevice, + unclaimDevice: unclaimDevice }; return service; @@ -332,4 +334,28 @@ function DeviceService($http, $q, $window, userService, attributeService, custom }); return deferred.promise; } + + function claimDevice(deviceName, deviceSecret, config) { + deviceSecret = deviceSecret || {}; + config = config || {}; + const deferred = $q.defer(); + const url = '/api/customer/device/' + deviceName + '/claim'; + $http.post(url, deviceSecret, config).then(function success(response) { + deferred.resolve(response.data); + }, function fail(rejection) { + deferred.reject(rejection); + }); + return deferred.promise; + } + + function unclaimDevice(deviceName) { + const deferred = $q.defer(); + const url = '/api/customer/device/' + deviceName + '/claim'; + $http.delete(url).then(function success(response) { + deferred.resolve(response.data); + }, function fail(rejection) { + deferred.reject(rejection); + }); + return deferred.promise; + } } From 1540f08695cfbd0733a19a922c0b3afd0a490535 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 19 Feb 2020 16:10:54 +0200 Subject: [PATCH 212/261] Add ability to use custom translations and labels containing apostrophe in table default sort order (#2397) --- ui/src/app/widget/lib/entities-table-widget.js | 4 ++-- ui/src/app/widget/lib/entities-table-widget.tpl.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js index 19b25ae724..e8cedd640e 100644 --- a/ui/src/app/widget/lib/entities-table-widget.js +++ b/ui/src/app/widget/lib/entities-table-widget.js @@ -196,9 +196,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP if (vm.settings.defaultSortOrder && vm.settings.defaultSortOrder.length) { vm.defaultSortOrder = vm.settings.defaultSortOrder; if (vm.settings.defaultSortOrder.charAt(0) === "-") { - vm.defaultSortOrder = "-'" + vm.settings.defaultSortOrder.substring(1) + "'"; + vm.defaultSortOrder = '-"' + utils.customTranslation(vm.settings.defaultSortOrder.substring(1), vm.settings.defaultSortOrder.substring(1)) + '"'; } else { - vm.defaultSortOrder = "'" + vm.settings.defaultSortOrder + "'"; + vm.defaultSortOrder = '"' + utils.customTranslation(vm.settings.defaultSortOrder, vm.settings.defaultSortOrder) + '"'; } } 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 ec91de1531..b6ba05531d 100644 --- a/ui/src/app/widget/lib/entities-table-widget.tpl.html +++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html @@ -41,7 +41,7 @@ - + From d5c3a9cc5dab65b88c46a4db6af00a054ddc223f Mon Sep 17 00:00:00 2001 From: Dmitriy Mushat <54553744+Dmitriymush@users.noreply.github.com> Date: Wed, 19 Feb 2020 16:14:18 +0200 Subject: [PATCH 213/261] fixed: focus on fullscreen for react schema grouped forms (#2400) --- .../components/react/json-form-ace-editor.jsx | 6 ++--- .../react/json-form-schema-form.jsx | 25 +++++++++++-------- ui/src/app/components/react/json-form.scss | 3 +++ 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/ui/src/app/components/react/json-form-ace-editor.jsx b/ui/src/app/components/react/json-form-ace-editor.jsx index 2329bbddf7..966419b3fe 100644 --- a/ui/src/app/components/react/json-form-ace-editor.jsx +++ b/ui/src/app/components/react/json-form-ace-editor.jsx @@ -83,9 +83,9 @@ class ThingsboardAceEditor extends React.Component { fixAceEditor(editor); } - onToggleFull() { + onToggleFull(groupId) { this.setState({ isFull: !this.state.isFull }); - this.props.onToggleFullscreen(); + this.props.onToggleFullscreen(groupId); this.updateAceEditorSize = true; } @@ -140,7 +140,7 @@ class ThingsboardAceEditor extends React.Component {
- + this.onToggleFull(this.props.groupId)}/>
+ return } - createSchema(theForm) { + createSchema(theForm, groupId) { let merged = utils.merge(this.props.schema, theForm, this.props.ignore, this.props.option); let mapper = this.mapper; if(this.props.mapper) { mapper = _.merge(this.mapper, this.props.mapper); } let forms = merged.map(function(form, index) { - return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper); + return this.builder(form, groupId, this.props.model, index, this.onChange, this.onColorClick, this.onIconClick, this.onToggleFullscreen, mapper); }.bind(this)); let formClass = 'SchemaForm'; - if (this.props.isFullscreen) { + if (this.props.isFullscreen && groupId === this.state.groupId) { formClass += ' SchemaFormFullscreen'; } @@ -131,7 +136,7 @@ class ThingsboardSchemaForm extends React.Component { if(this.props.groupInfoes&&this.props.groupInfoes.length>0){ let content=[]; for(let info of this.props.groupInfoes){ - let forms = this.createSchema(this.props.form[info.formIndex]); + let forms = this.createSchema(this.props.form[info.formIndex], info.formIndex); let item = ; content.push(item); } @@ -165,4 +170,4 @@ class ThingsboardSchemaGroup extends React.Component{
{this.props.forms}
); } -} +} diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss index 85825abf42..91c14d51ff 100644 --- a/ui/src/app/components/react/json-form.scss +++ b/ui/src/app/components/react/json-form.scss @@ -24,12 +24,15 @@ $input-label-float-scale: .75 !default; .tb-fullscreen { [name="ReactSchemaForm"] { .SchemaForm { + display: none; + &.SchemaFormFullscreen { position: absolute; top: 0; right: 0; bottom: 0; left: 0; + display: block; > div:not(.fullscreen-form-field) { display: none !important; From 284638e09383e0e8fe54fa539952cb722d4075fb Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 19 Feb 2020 16:48:25 +0200 Subject: [PATCH 214/261] Fix translate language Latvian --- ui/src/app/locale/locale.constant-lv_LV.json | 3029 +++++++++--------- 1 file changed, 1514 insertions(+), 1515 deletions(-) diff --git a/ui/src/app/locale/locale.constant-lv_LV.json b/ui/src/app/locale/locale.constant-lv_LV.json index 60304f6af2..7fafb97af2 100644 --- a/ui/src/app/locale/locale.constant-lv_LV.json +++ b/ui/src/app/locale/locale.constant-lv_LV.json @@ -1,1683 +1,1682 @@ { "access": { - "unauthorized": "Unauthorized", - "unauthorized-access": "Unauthorized Access", - "unauthorized-access-text": "You should sign in to have access to this resource!", - "access-forbidden": "Access Forbidden", - "access-forbidden-text": "You haven't access rights to this location!
Try to sign in with different user if you still wish to gain access to this location.", - "refresh-token-expired": "Session has expired", - "refresh-token-failed": "Unable to refresh session" + "unauthorized": "Neatļauta", + "unauthorized-access": "Neatļauta piekļuve", + "unauthorized-access-text": "Lai piekļūtu šim resursam, jums jāpierakstās!", + "access-forbidden": "Piekļuve aizliegta", + "access-forbidden-text": "Jums nav piekļuves tiesību!
Mēģiniet pierakstīties ar citu lietotājvārdu.", + "refresh-token-expired": "Sesija ir beigusies", + "refresh-token-failed": "Nevar atjaunot sesiju" }, "action": { - "activate": "Activate", - "suspend": "Suspend", - "save": "Save", - "saveAs": "Save as", - "cancel": "Cancel", + "activate": "Aktivizēt", + "suspend": "Apturēt", + "save": "Saglabāt", + "saveAs": "Saglabāt kā", + "cancel": "Atcelt", "ok": "OK", - "delete": "Delete", - "add": "Add", - "yes": "Yes", - "no": "No", - "update": "Update", - "remove": "Remove", - "search": "Search", - "clear-search": "Clear search", - "assign": "Assign", - "unassign": "Unassign", - "share": "Share", - "make-private": "Make private", - "apply": "Apply", - "apply-changes": "Apply changes", - "edit-mode": "Edit mode", - "enter-edit-mode": "Enter edit mode", - "decline-changes": "Decline changes", - "close": "Close", - "back": "Back", - "run": "Run", - "sign-in": "Sign in!", - "edit": "Edit", - "view": "View", - "create": "Create", - "drag": "Drag", - "refresh": "Refresh", - "undo": "Undo", - "copy": "Copy", - "paste": "Paste", - "copy-reference": "Copy reference", - "paste-reference": "Paste reference", - "import": "Import", - "export": "Export", - "share-via": "Share via {{provider}}", - "continue": "Continue" + "delete": "Dzēst", + "add": "Pievienot", + "yes": "Jā", + "no": "Nē", + "update": "Atjaunināt", + "remove": "Noņemt", + "search": "Meklēt", + "clear-search": "Notīrīt meklēšanu", + "assign": "Piešķirt", + "unassign": "Noņemt", + "share": "Dalīties", + "make-private": "Padarīt privātu", + "apply": "Pielietot", + "apply-changes": "Pielietot izmaiņas", + "edit-mode": "Rediģēšanas režīms", + "enter-edit-mode": "Ievadiet rediģēšanas režīmu", + "decline-changes": "Noraidīt izmaiņas", + "close": "Aizvērt", + "back": "Atpakaļ", + "run": "Uz priekšu", + "sign-in": "Pierakstīties!", + "edit": "Rediģēt", + "view": "Skatīt", + "create": "Radīt", + "drag": "Velciet", + "refresh": "Atjaunot", + "undo": "Atsaukt", + "copy": "Kopēt", + "paste": "Ielīmēt", + "copy-reference": "Kopija atsauce", + "paste-reference": "Ielīmēt atsauce", + "import": "Importēt", + "export": "Eksportēt", + "share-via": "Dalīties caur {{pakalpojuma sniedzējs}}", + "continue": "Turpināt" }, "aggregation": { - "aggregation": "Aggregation", - "function": "Data aggregation function", - "limit": "Max values", - "group-interval": "Grouping interval", + "aggregation": "Sakopojums", + "function": "Datu sakopojuma funkcija", + "limit": "Limits", + "group-interval": "Grupas intervāls", "min": "Min", "max": "Max", - "avg": "Average", + "avg": "Vidējais", "sum": "Sum", - "count": "Count", - "none": "None" + "count": "Skaits", + "none": "Neviena" }, "admin": { - "general": "General", - "general-settings": "General Settings", - "outgoing-mail": "Mail Server", - "outgoing-mail-settings": "Outgoing Mail Server Settings", - "system-settings": "System Settings", - "test-mail-sent": "Test mail was successfully sent!", - "base-url": "Base URL", - "base-url-required": "Base URL is required.", - "mail-from": "Mail From", - "mail-from-required": "Mail From is required.", - "smtp-protocol": "SMTP 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-invalid": "That doesn't look like a valid smtp port.", - "timeout-msec": "Timeout (msec)", - "timeout-required": "Timeout is required.", - "timeout-invalid": "That doesn't look like a valid timeout.", - "enable-tls": "Enable TLS", - "tls-version": "TLS version", - "send-test-mail": "Send test mail" + "general": "Vispārīgi", + "general-settings": "Vispārīgie iestatījumi", + "outgoing-mail": "Pasta serveris", + "outgoing-mail-settings": "Izejošā pasta servera iestatījumi", + "system-settings": "Sistēmas iestatījumi", + "test-mail-sent": "Testa pasts sekmīgi nosūtīts!", + "base-url": "pamata URL", + "base-url-required": "Pamata URL ir nepieciešams.", + "mail-from": "Pasts no", + "mail-from-required": "Pasts no ir nepieciešams.", + "smtp-protocol": "SMTP protokols", + "smtp-host": "SMTP saimnieks", + "smtp-host-required": "SMTP saimnieks ir nepieciešams.", + "smtp-port": "SMTP ports", + "smtp-port-required": "Jums vajag nodrošināt SMTP portu.", + "smtp-port-invalid": "Tas neizskatās pēc atļauta SMTP porta.", + "timeout-msec": "Pārtraukums (msec)", + "timeout-required": "Pārtraukums ir nepieciešams.", + "timeout-invalid": "Tas neizskatās pēc atļauta pārtraukuma.", + "enable-tls": "Iespējot TLS", + "send-test-mail": "Nosūtīt testa pastu" }, "alarm": { - "alarm": "Alarm", - "alarms": "Alarms", - "select-alarm": "Select alarm", - "no-alarms-matching": "No alarms matching '{{entity}}' were found.", - "alarm-required": "Alarm is required", - "alarm-status": "Alarm status", + "alarm": "Trauksme", + "alarms": "Trauksmes", + "select-alarm": "Atlasīt trauksmi", + "no-alarms-matching": "Nav atbilstošu trauksmju '{{entity}}' .", + "alarm-required": "Trauksme ir nepieciešama", + "alarm-status": "Trauksmes statuss", "search-status": { - "ANY": "Any", - "ACTIVE": "Active", - "CLEARED": "Cleared", - "ACK": "Acknowledged", - "UNACK": "Unacknowledged" + "ANY": "Jebkura", + "ACTIVE": "Aktīvs", + "CLEARED": "Dzēsts", + "ACK": "Apstiprināts", + "UNACK": "Neapstiprināts" }, "display-status": { - "ACTIVE_UNACK": "Active Unacknowledged", - "ACTIVE_ACK": "Active Acknowledged", - "CLEARED_UNACK": "Cleared Unacknowledged", - "CLEARED_ACK": "Cleared Acknowledged" + "ACTIVE_UNACK": "Aktīvs Neapstiprināts", + "ACTIVE_ACK": "Aktīvs Apstiprināts", + "CLEARED_UNACK": "Dzēsts Neapstiprināts", + "CLEARED_ACK": "Dzēsts Apstiprināts" }, - "no-alarms-prompt": "No alarms found", - "created-time": "Created time", - "type": "Type", - "severity": "Severity", - "originator": "Originator", - "originator-type": "Originator type", - "details": "Details", - "status": "Status", - "alarm-details": "Alarm details", - "start-time": "Start time", - "end-time": "End time", - "ack-time": "Acknowledged time", - "clear-time": "Cleared time", - "severity-critical": "Critical", - "severity-major": "Major", - "severity-minor": "Minor", - "severity-warning": "Warning", - "severity-indeterminate": "Indeterminate", - "acknowledge": "Acknowledge", - "clear": "Clear", - "search": "Search alarms", - "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selected", - "no-data": "No data to display", - "polling-interval": "Alarms polling interval (sec)", - "polling-interval-required": "Alarms polling interval is required.", - "min-polling-interval-message": "At least 1 sec polling interval is allowed.", - "aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }", - "aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?", - "aknowledge-alarm-title": "Acknowledge Alarm", - "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?", - "clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }", - "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", - "clear-alarm-title": "Clear Alarm", - "clear-alarm-text": "Are you sure you want to clear Alarm?", - "alarm-status-filter": "Alarm Status Filter" + "no-alarms-prompt": "Trauksmes nav atrastas", + "created-time": "Izveidošanas laiks", + "type": "Tips", + "severity": "Smaguma pakāpe", + "originator": "Iniciātors", + "originator-type": "Iniciātora tips", + "details": "Detaļas", + "status": "Statuss", + "alarm-details": "Trauksmes detaļas", + "start-time": "Sākuma laiks", + "end-time": "Beigu laiks", + "ack-time": "Apstiprinājuma laiks", + "clear-time": "Notīrīšanas laiks", + "severity-critical": "Smaguma pakāpe - kritiska", + "severity-major": "Būtiska", + "severity-minor": "Minora", + "severity-warning": "Brīdinājums", + "severity-indeterminate": "Nenoteikts", + "acknowledge": "Apstiprināt", + "clear": "Notīrīt", + "search": "Meklēt trauksmes", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# trauksmes} } selected", + "no-data": "Nav datu ko attēlot", + "polling-interval": "Trauksmju pārbaužu intervāls (sec)", + "polling-interval-required": "Trauksmju pārbaužu intervāls ir nepieciešams.", + "min-polling-interval-message": "Vismaz 1 sekundes pārbaužu intervāls ir atļauts.", + "aknowledge-alarms-title": "Apstiprināt { count, plural, 1 {1 alarm} other {# trauksmes} }", + "aknowledge-alarms-text": "Vai Jūs tiešām vēlaties apstirpināt { count, plural, 1 {1 alarm} other {# trauksmes} }?", + "aknowledge-alarm-title": "Apstiprināt trauksmi", + "aknowledge-alarm-text": "Vai Jūs tiešām vēlaties apstiprināt trauksmi?", + "clear-alarms-title": "Dzēst { count, plural, 1 {1 alarm} other {# trauksmes} }", + "clear-alarms-text": "Vai Jūs tiešām vēlaties dzēst { count, plural, 1 {1 alarm} other {# trauksmes} }?", + "clear-alarm-title": "Dzēst trauksmi", + "clear-alarm-text": "Vai Jūs tiešām vēlaties dzēst trauksmi?", + "alarm-status-filter": "Trauksmes statusa filtrs" }, "alias": { - "add": "Add alias", - "edit": "Edit alias", - "name": "Alias name", - "name-required": "Alias name is required", - "duplicate-alias": "Alias with same name is already exists.", - "filter-type-single-entity": "Single entity", - "filter-type-entity-list": "Entity list", - "filter-type-entity-name": "Entity name", - "filter-type-state-entity": "Entity from dashboard state", - "filter-type-state-entity-description": "Entity taken from dashboard state parameters", - "filter-type-asset-type": "Asset type", - "filter-type-asset-type-description": "Assets of type '{{assetType}}'", - "filter-type-asset-type-and-name-description": "Assets of type '{{assetType}}' and with name starting with '{{prefix}}'", - "filter-type-device-type": "Device type", - "filter-type-device-type-description": "Devices of type '{{deviceType}}'", - "filter-type-device-type-and-name-description": "Devices of type '{{deviceType}}' and with name starting with '{{prefix}}'", - "filter-type-entity-view-type": "Entity View type", - "filter-type-entity-view-type-description": "Entity Views of type '{{entityView}}'", - "filter-type-entity-view-type-and-name-description": "Entity Views of type '{{entityView}}' and with name starting with '{{prefix}}'", - "filter-type-relations-query": "Relations query", - "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-asset-search-query": "Asset search query", - "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-device-search-query": "Device search query", - "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-entity-view-search-query": "Entity view search query", - "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", - "entity-filter": "Entity filter", - "resolve-multiple": "Resolve as multiple entities", - "filter-type": "Filter type", - "filter-type-required": "Filter type is required.", - "entity-filter-no-entity-matched": "No entities matching specified filter were found.", - "no-entity-filter-specified": "No entity filter specified", - "root-state-entity": "Use dashboard state entity as root", - "root-entity": "Root entity", - "state-entity-parameter-name": "State entity parameter name", - "default-state-entity": "Default state entity", - "default-entity-parameter-name": "By default", - "max-relation-level": "Max relation level", - "unlimited-level": "Unlimited level", - "state-entity": "Dashboard state entity", - "all-entities": "All entities", - "any-relation": "any" + "add": "Pievienot segvārdu", + "edit": "Rediģēt segvārdu", + "name": "Segvārda nosaukums", + "name-required": "Segvārda nosaukums vārds ir nepieciešams", + "duplicate-alias": "Segvārds ar tādu pašu nosaukumu jau eksistē.", + "filter-type-single-entity": "Viena vienība", + "filter-type-entity-list": "Vienību saraksts", + "filter-type-entity-name": "Vienības vārds", + "filter-type-state-entity": "Vienība no paneļa stāvokļa", + "filter-type-state-entity-description": "Vienība ņemta no paneļa stāvokļa parametriem", + "filter-type-asset-type": "Aktīvu tips", + "filter-type-asset-type-description": "Aktīvu tipi '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Aktīvu tips '{{assetType}}' un ar vārdu sākot ar '{{prefix}}'", + "filter-type-device-type": "Iekārtas tips", + "filter-type-device-type-description": "Iekārtas tipi '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Iekārtas tipi '{{deviceType}}' un ar vārdu sākot ar '{{prefix}}'", + "filter-type-entity-view-type": "Vienības skata tips", + "filter-type-entity-view-type-description": "Vienības skats tipam '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Vienības skats tipam '{{entityView}}' un ar vārdu sākot ar '{{prefix}}'", + "filter-type-relations-query": "Attiecību vaicājums", + "filter-type-relations-query-description": "{{entities}} kam ir {{relationType}} attiecība {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Aktīvu meklēšanas vaicājums", + "filter-type-asset-search-query-description": "Aktīvi ar tipu {{assetTypes}} kam ir {{relationType}} attiecība {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Iekārtu meklēšanas vaicājums", + "filter-type-device-search-query-description": "Iekārtas ar tipu {{deviceTypes}} kam ir {{relationType}} attiecība {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Vienības skata meklēšanas vaicājuma", + "filter-type-entity-view-search-query-description": "Vienību skats ar tipu {{entityViewTypes}} kam ir {{relationType}} attiecība {{direction}} {{rootEntity}}", + "entity-filter": "Vienību filtrs", + "resolve-multiple": "Atrisināt kā daudzas vienības", + "filter-type": "Filtra tips", + "filter-type-required": "Filtra tips ir nepieciešams.", + "entity-filter-no-entity-matched": "Nav atrastas vienības kam atbilst filtru iestatījumi.", + "no-entity-filter-specified": "Nav vienību filtrs specificēts", + "root-state-entity": "Lieto paneļa statusa vienību kā sakni ", + "root-entity": "Saknes vienības", + "state-entity-parameter-name": "Statusa vienības parametra vārds", + "default-state-entity": "Noklusējuma statusa vienība", + "default-entity-parameter-name": "Pēc noklusējuma", + "max-relation-level": "Maksimālais attiecību līmenis", + "unlimited-level": "Nelimitēts līmenis", + "state-entity": "Paneļa statusa vienība", + "all-entities": "Visas vienības", + "any-relation": "Jebkura" }, "asset": { - "asset": "Asset", - "assets": "Assets", - "management": "Asset management", - "view-assets": "View Assets", - "add": "Add Asset", - "assign-to-customer": "Assign to customer", - "assign-asset-to-customer": "Assign Asset(s) To Customer", - "assign-asset-to-customer-text": "Please select the assets to assign to the customer", - "no-assets-text": "No assets found", - "assign-to-customer-text": "Please select the customer to assign the asset(s)", - "public": "Public", - "assignedToCustomer": "Assigned to customer", - "make-public": "Make asset public", - "make-private": "Make asset private", - "unassign-from-customer": "Unassign from customer", - "delete": "Delete asset", - "asset-public": "Asset is public", - "asset-type": "Asset type", - "asset-type-required": "Asset type is required.", - "select-asset-type": "Select asset type", - "enter-asset-type": "Enter asset type", - "any-asset": "Any asset", - "no-asset-types-matching": "No asset types matching '{{entitySubtype}}' were found.", - "asset-type-list-empty": "No asset types selected.", - "asset-types": "Asset types", - "name": "Name", - "name-required": "Name is required.", - "description": "Description", - "type": "Type", - "type-required": "Type is required.", - "details": "Details", - "events": "Events", - "add-asset-text": "Add new asset", - "asset-details": "Asset details", - "assign-assets": "Assign assets", - "assign-assets-text": "Assign { count, plural, 1 {1 asset} other {# assets} } to customer", - "delete-assets": "Delete assets", - "unassign-assets": "Unassign assets", - "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer", - "assign-new-asset": "Assign new asset", - "delete-asset-title": "Are you sure you want to delete the asset '{{assetName}}'?", - "delete-asset-text": "Be careful, after the confirmation the asset and all related data will become unrecoverable.", - "delete-assets-title": "Are you sure you want to delete { count, plural, 1 {1 asset} other {# assets} }?", - "delete-assets-action-title": "Delete { count, plural, 1 {1 asset} other {# assets} }", - "delete-assets-text": "Be careful, after the confirmation all selected assets will be removed and all related data will become unrecoverable.", - "make-public-asset-title": "Are you sure you want to make the asset '{{assetName}}' public?", - "make-public-asset-text": "After the confirmation the asset and all its data will be made public and accessible by others.", - "make-private-asset-title": "Are you sure you want to make the asset '{{assetName}}' private?", - "make-private-asset-text": "After the confirmation the asset and all its data will be made private and won't be accessible by others.", - "unassign-asset-title": "Are you sure you want to unassign the asset '{{assetName}}'?", - "unassign-asset-text": "After the confirmation the asset will be unassigned and won't be accessible by the customer.", - "unassign-asset": "Unassign asset", - "unassign-assets-title": "Are you sure you want to unassign { count, plural, 1 {1 asset} other {# assets} }?", - "unassign-assets-text": "After the confirmation all selected assets will be unassigned and won't be accessible by the customer.", - "copyId": "Copy asset Id", - "idCopiedMessage": "Asset Id has been copied to clipboard", - "select-asset": "Select asset", - "no-assets-matching": "No assets matching '{{entity}}' were found.", - "asset-required": "Asset is required", - "name-starts-with": "Asset name starts with", - "import": "Import assets", - "asset-file": "Asset file" + "asset": "Aktīvs", + "assets": "Aktīvi", + "management": "Aktīvu pārvaldība", + "view-assets": "Skatīt aktīvus", + "add": "Pievienot aktīvu", + "assign-to-customer": "Pieškirt klientam", + "assign-asset-to-customer": "Piešķirt aktīvu klientam", + "assign-asset-to-customer-text": "Lūdzu izvēlēties aktīvu lai pieškirtu klientam", + "no-assets-text": "Aktīvi nav atrasti", + "assign-to-customer-text": "Lūdzu izvēlēties klientu lai pieškirtu aktīvu", + "public": "Publisks", + "assignedToCustomer": "Pieškirts klientam", + "make-public": "Veidot aktīvu publisku", + "make-private": "Veidot aktīvu privātu", + "unassign-from-customer": "Noņemt klientam", + "delete": "Dzēst aktīvu", + "asset-public": "Aktīvs ir publisks", + "asset-type": "Aktīva tips", + "asset-type-required": "Aktīva tips ir nepieciešams.", + "select-asset-type": "Izvēlies aktīva tipu", + "enter-asset-type": "Ievadi aktīva tipu", + "any-asset": "Jebkurš aktīvs", + "no-asset-types-matching": "Nav atbilstošs aktīvu tips '{{entitySubtype}}' atrasts.", + "asset-type-list-empty": "Nav aktīvu tipi izvēlēti.", + "asset-types": "Aktīvu tipi", + "name": "Vārds", + "name-required": "Vārds ir nepieciešams.", + "description": "Apraksts", + "type": "Tips", + "type-required": "Tips ir nepieciešams.", + "details": "Detaļas", + "events": "Notikumi", + "add-asset-text": "Pievieno jaunu aktīvu", + "asset-details": "Aktīvu detaļas", + "assign-assets": "Piešķirt aktīvus", + "assign-assets-text": "Piešķirt { count, plural, 1 {1 asset} other {# aktīvus} } klientam", + "delete-assets": "Dzēst aktīvus", + "unassign-assets": "Noņemt aktīvus", + "unassign-assets-action-title": "Noņemt { count, plural, 1 {1 asset} other {# aktīvus} } no klienta", + "assign-new-asset": "Pieškirt jaunu aktīvu", + "delete-asset-title": "Vai esat pārliecināts,ka vēlaties dzēst aktīvu '{{assetName}}'?", + "delete-asset-text": "Esiet uzmanīgs, pēc apstiprināšanas aktīvs un saistītie dati nebūs atjaunojami.", + "delete-assets-title": "Vai esat pārliecināts ka vēlaties dzēst { count, plural, 1 {1 asset} other {# aktīvus} }?", + "delete-assets-action-title": "Dzēst { count, plural, 1 {1 asset} citu {# aktīvus} }", + "delete-assets-text": "Esiet uzmanīgs, pēc apstiprinājuma visi izvēlētie aktīvi tiks dzēsti un saistītā informācija nebūs atjaunojama.", + "make-public-asset-title": "Vai esat pārliecināts ka vēlaties aktīvu '{{assetName}}' veidot publisku?", + "make-public-asset-text": "Pēc apstiprinājuma aktīvs un tā dati tiks publiski pieejami.", + "make-private-asset-title": "Vai esat pārliecināts ka vēlaties aktīvu '{{assetName}}' veidot privātu?", + "make-private-asset-text": "Pēc apstiprinājums aktīvs un tā saistītie dati būs privāti un nebūs pieejami citiem.", + "unassign-asset-title": "Vai esat pārliecināts ka vēlaties noņemt aktīvu '{{assetName}}'?", + "unassign-asset-text": "Pēc apstiprināšanas aktīvs tiks noņemts un nebūs pieejams klientiem.", + "unassign-asset": "Noņemt aktīvu", + "unassign-assets-title": "Vai esat pārliecināts ka vēlaties noņemt { count, plural, 1 {1 asset} citu {# aktīvus} }?", + "unassign-assets-text": "Pēc apstiprināšanas visi izvēlētie aktīvi būs noņemti un nebūs pieejami klientiem.", + "copyId": "Kopēt aktīva Id", + "idCopiedMessage": "Aktīva Id ir kopēts uz starpliktuvi", + "select-asset": "Atlasīt aktīvu", + "no-assets-matching": "Nav atbilstošs aktīvs '{{entity}}' atrasts.", + "asset-required": "Aktīvs ir nepieciešams", + "name-starts-with": "Aktīva vārds sākas ar", + "import": "Importēt aktīvus", + "asset-file": "Aktīvu fails" }, "attribute": { - "attributes": "Attributes", - "latest-telemetry": "Latest telemetry", - "attributes-scope": "Entity attributes scope", - "scope-latest-telemetry": "Latest telemetry", - "scope-client": "Client attributes", - "scope-server": "Server attributes", - "scope-shared": "Shared attributes", - "add": "Add attribute", - "key": "Key", - "last-update-time": "Last update time", - "key-required": "Attribute key is required.", - "value": "Value", - "value-required": "Attribute value is required.", - "delete-attributes-title": "Are you sure you want to delete { count, plural, 1 {1 attribute} other {# attributes} }?", - "delete-attributes-text": "Be careful, after the confirmation all selected attributes will be removed.", - "delete-attributes": "Delete attributes", - "enter-attribute-value": "Enter attribute value", - "show-on-widget": "Show on widget", - "widget-mode": "Widget mode", - "next-widget": "Next widget", - "prev-widget": "Previous widget", - "add-to-dashboard": "Add to dashboard", - "add-widget-to-dashboard": "Add widget to dashboard", - "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selected", - "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selected" + "attributes": "Attribūti", + "latest-telemetry": "Jaunākā telemetrija", + "attributes-scope": "Vienības atribūtu darbības joma", + "scope-latest-telemetry": "Jaunākā telemetrija", + "scope-client": "Klientu atribūti", + "scope-server": "Servera atribūti", + "scope-shared": "Dalītie atribūti", + "add": "Pievieno atribūtu", + "key": "Atslēga", + "last-update-time": "Pēdēja atjaunojuma laiks", + "key-required": "Atribūta atslēga ir nepieciešama.", + "value": "Vērtība", + "value-required": "Atribūta vērtība ir nepieciešama.", + "delete-attributes-title": "Vai esat pārliecināts ka vēlaties dzēst { count, plural, 1 {1 attribute} other {# attribūtus} }?", + "delete-attributes-text": "Esiet uzmanīgs, pēc apstiprinājuma visi izvēlētie atribūti tiks dzēsti.", + "delete-attributes": "Dzēst atribūtu", + "enter-attribute-value": "Ievadiet atribūta vērtību", + "show-on-widget": "Parādīt logrīkā", + "widget-mode": "Logrīka režīms", + "next-widget": "Nākamais logrīks", + "prev-widget": "Iepriekšējais logrīks", + "add-to-dashboard": "Pievienot panelim", + "add-widget-to-dashboard": "Pievienot logrīku panelim", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# atribūtus} } izvēlētajam", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetrijas vienības} } izvēlētas" }, "audit-log": { - "audit": "Audit", - "audit-logs": "Audit Logs", - "timestamp": "Timestamp", - "entity-type": "Entity Type", - "entity-name": "Entity Name", - "user": "User", - "type": "Type", - "status": "Status", - "details": "Details", - "type-added": "Added", - "type-deleted": "Deleted", - "type-updated": "Updated", - "type-attributes-updated": "Attributes updated", - "type-attributes-deleted": "Attributes deleted", - "type-rpc-call": "RPC call", - "type-credentials-updated": "Credentials updated", - "type-assigned-to-customer": "Assigned to Customer", - "type-unassigned-from-customer": "Unassigned from Customer", - "type-activated": "Activated", - "type-suspended": "Suspended", - "type-credentials-read": "Credentials read", - "type-attributes-read": "Attributes read", - "type-relation-add-or-update": "Relation updated", - "type-relation-delete": "Relation deleted", - "type-relations-delete": "All relation deleted", - "type-alarm-ack": "Acknowledged", - "type-alarm-clear": "Cleared", - "status-success": "Success", - "status-failure": "Failure", - "audit-log-details": "Audit log details", - "no-audit-logs-prompt": "No logs found", - "action-data": "Action data", - "failure-details": "Failure details", - "search": "Search audit logs", - "clear-search": "Clear search" + "audit": "Audits", + "audit-logs": "Audita logs", + "timestamp": "Laika zīmogs", + "entity-type": "Vienības tips", + "entity-name": "Vienības vārds", + "user": "Lietotājs", + "type": "Tips", + "status": "Statuss", + "details": "Detaļas", + "type-added": "Pievienots", + "type-deleted": "Dzēsts", + "type-updated": "Atjaunots", + "type-attributes-updated": "Atribūti atjaunoti", + "type-attributes-deleted": "Atribūti dzēsti", + "type-rpc-call": "RPC izsaukumi", + "type-credentials-updated": "Akreditācijas dati atjaunoti", + "type-assigned-to-customer": "Pieškirts klientam", + "type-unassigned-from-customer": "Noņemts no klienta", + "type-activated": "Aktivizēts", + "type-suspended": "Apturēts", + "type-credentials-read": "Akreditācijas datu nolasījums", + "type-attributes-read": "Atribūtu nolasījums", + "type-relation-add-or-update": "Attiecība atjaunota", + "type-relation-delete": "Atiecība dzēsta", + "type-relations-delete": "Visas attiecības dzēstas", + "type-alarm-ack": "Apstiprinājums", + "type-alarm-clear": "Notīrīts", + "status-success": "Sekmīgi", + "status-failure": "Neveiksme", + "audit-log-details": "Audita loga detaļas", + "no-audit-logs-prompt": "Nav logu atrastu", + "action-data": "Aktivitāšu dati", + "failure-details": "Neveiksmju detaļas", + "search": "Meklēt audita logus", + "clear-search": "Notīrīt meklēšanu" }, "confirm-on-exit": { - "message": "You have unsaved changes. Are you sure you want to leave this page?", - "html-message": "You have unsaved changes.
Are you sure you want to leave this page?", - "title": "Unsaved changes" + "message": "Jums ir nesaglabātas izmaiņas. Vai tiešām vēlaties pamest šo lapu?", + "html-message": "Jums ir nesaglabātas izmaiņas.
Vai tiešām vēlaties pamest šo lapu?", + "title": "Nesaglabātas izmaiņas" }, "contact": { - "country": "Country", - "city": "City", - "state": "State / Province", - "postal-code": "Zip / Postal Code", - "postal-code-invalid": "Invalid Zip / Postal Code format.", - "address": "Address", - "address2": "Address 2", - "phone": "Phone", + "country": "Valsts", + "city": "Pilsēta", + "state": "Štats/Province", + "postal-code": "Zip / Pasta kods", + "postal-code-invalid": "Invalīds Zip / Pasta koda formāts.", + "address": "Adrese", + "address2": "Adrese 2", + "phone": "Telefons", "email": "Email", - "no-address": "No address" + "no-address": "Nav adreses" }, "common": { - "username": "Username", - "password": "Password", - "enter-username": "Enter username", - "enter-password": "Enter password", - "enter-search": "Enter search" + "username": "Lietotājvārdse", + "password": "Parole", + "enter-username": "Ievadiet lietotājvārdu", + "enter-password": "Ievadiet paroli", + "enter-search": "Ievadiet meklēt" }, "content-type": { "json": "Json", - "text": "Text", - "binary": "Binary (Base64)" + "text": "Teksts", + "binary": "Bināri (Base64)" }, "customer": { - "customer": "Customer", - "customers": "Customers", - "management": "Customer management", - "dashboard": "Customer Dashboard", - "dashboards": "Customer Dashboards", - "devices": "Customer Devices", - "entity-views": "Customer Entity Views", - "assets": "Customer Assets", - "public-dashboards": "Public Dashboards", - "public-devices": "Public Devices", - "public-assets": "Public Assets", - "public-entity-views": "Public Entity Views", - "add": "Add Customer", - "delete": "Delete customer", - "manage-customer-users": "Manage customer users", - "manage-customer-devices": "Manage customer devices", - "manage-customer-dashboards": "Manage customer dashboards", - "manage-public-devices": "Manage public devices", - "manage-public-dashboards": "Manage public dashboards", - "manage-customer-assets": "Manage customer assets", - "manage-public-assets": "Manage public assets", - "add-customer-text": "Add new customer", - "no-customers-text": "No customers found", - "customer-details": "Customer details", - "delete-customer-title": "Are you sure you want to delete the customer '{{customerTitle}}'?", - "delete-customer-text": "Be careful, after the confirmation the customer and all related data will become unrecoverable.", - "delete-customers-title": "Are you sure you want to delete { count, plural, 1 {1 customer} other {# customers} }?", - "delete-customers-action-title": "Delete { count, plural, 1 {1 customer} other {# customers} }", - "delete-customers-text": "Be careful, after the confirmation all selected customers will be removed and all related data will become unrecoverable.", - "manage-users": "Manage users", - "manage-assets": "Manage assets", - "manage-devices": "Manage devices", - "manage-dashboards": "Manage dashboards", - "title": "Title", - "title-required": "Title is required.", - "description": "Description", - "details": "Details", - "events": "Events", - "copyId": "Copy customer Id", - "idCopiedMessage": "Customer Id has been copied to clipboard", - "select-customer": "Select customer", - "no-customers-matching": "No customers matching '{{entity}}' were found.", - "customer-required": "Customer is required", - "select-default-customer": "Select default customer", - "default-customer": "Default customer", - "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level" + "customer": "Klients", + "customers": "Klienti", + "management": "Klientu pārvaldība", + "dashboard": "Klientu panelis", + "dashboards": "Klientu paneļi", + "devices": "Klienta iekārtas", + "entity-views": "Klienta vienību skati", + "assets": "Klienta aktīvi", + "public-dashboards": "Publiskie paneļi", + "public-devices": "Publiskās iekārtas", + "public-assets": "Publiskie aktīvi", + "public-entity-views": "Publisko vienību skati", + "add": "Pievienot klientu", + "delete": "Dzēst klientu", + "manage-customer-users": "Pārvaldīt klienta lietotājus", + "manage-customer-devices": "Pārvaldīt klienta iekārtas", + "manage-customer-dashboards": "Pārvaldīt klienta paneļus", + "manage-public-devices": "Pārvaldīt publiskās iekārtas", + "manage-public-dashboards": "Pārvaldīt publiskos paneļus", + "manage-customer-assets": "Pārvaldīt klienta aktīvus", + "manage-public-assets": "Pārvaldīt publiskos aktīvus", + "add-customer-text": "Pievienot jaunu klientu", + "no-customers-text": "Nav klienti atrasti", + "customer-details": "Klienta detaļas", + "delete-customer-title": "Vai esat pārliecināts, ka vēlaties dzēst klientu '{{customerTitle}}'?", + "delete-customer-text": "Esiet uzmanīgs, pēc apstiprinājuma klients un tā saistītie dati nebūs atjaunojami.", + "delete-customers-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 customer} other {# klientus} }?", + "delete-customers-action-title": "Dzēst { count, plural, 1 {1 customer} other {# klientus} }", + "delete-customers-text": "Esiet uzmanīgs, pēc apstiprinājuma visi izvēlētie klienti tisk dzēsti un to saistītie dati nebūs atjaunojami.", + "manage-users": "Pārvaldīt lietotājus", + "manage-assets": "Pārvaldīt aktīvus", + "manage-devices": "Pārvaldīt iekārtas", + "manage-dashboards": "Pārvaldīt paneļus", + "title": "Virsraksts", + "title-required": "Virsraksts ir nepieciešams.", + "description": "Apraksts", + "details": "Detaļas", + "events": "Notikumi", + "copyId": "Kopēt klienta Id", + "idCopiedMessage": "Klienta Id ir kopēts uz starpliktuvi", + "select-customer": "Atlasīt klientu", + "no-customers-matching": "Nav atbilstoši klienti '{{entity}}' atrasti.", + "customer-required": "Klients ir nepieciešams", + "select-default-customer": "Atlasīt pamata klientu", + "default-customer": "Pamata klients", + "default-customer-required": "Pamata klients ir nepieciešams lai atkļūdotu paneli īrnieka līmenī" }, "datetime": { - "date-from": "Date from", - "time-from": "Time from", - "date-to": "Date to", - "time-to": "Time to" + "date-from": "Datums no", + "time-from": "Laiks no", + "date-to": "Datums līdz", + "time-to": "Laiks līdz" }, "dashboard": { - "dashboard": "Dashboard", - "dashboards": "Dashboards", - "management": "Dashboard management", - "view-dashboards": "View Dashboards", - "add": "Add Dashboard", - "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer", - "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer", - "assign-to-customer-text": "Please select the customer to assign the dashboard(s)", - "assign-to-customer": "Assign to customer", - "unassign-from-customer": "Unassign from customer", - "make-public": "Make dashboard public", - "make-private": "Make dashboard private", - "manage-assigned-customers": "Manage assigned customers", - "assigned-customers": "Assigned customers", - "assign-to-customers": "Assign Dashboard(s) To Customers", - "assign-to-customers-text": "Please select the customers to assign the dashboard(s)", - "unassign-from-customers": "Unassign Dashboard(s) From Customers", - "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)", - "no-dashboards-text": "No dashboards found", - "no-widgets": "No widgets configured", - "add-widget": "Add new widget", - "title": "Title", - "select-widget-title": "Select widget", - "select-widget-subtitle": "List of available widget types", - "delete": "Delete dashboard", - "title-required": "Title is required.", - "description": "Description", - "details": "Details", - "dashboard-details": "Dashboard details", - "add-dashboard-text": "Add new dashboard", - "assign-dashboards": "Assign dashboards", - "assign-new-dashboard": "Assign new dashboard", - "assign-dashboards-text": "Assign { count, plural, 1 {1 dashboard} other {# dashboards} } to customers", - "unassign-dashboards-action-text": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customers", - "delete-dashboards": "Delete dashboards", - "unassign-dashboards": "Unassign dashboards", - "unassign-dashboards-action-title": "Unassign { count, plural, 1 {1 dashboard} other {# dashboards} } from customer", - "delete-dashboard-title": "Are you sure you want to delete the dashboard '{{dashboardTitle}}'?", - "delete-dashboard-text": "Be careful, after the confirmation the dashboard and all related data will become unrecoverable.", - "delete-dashboards-title": "Are you sure you want to delete { count, plural, 1 {1 dashboard} other {# dashboards} }?", - "delete-dashboards-action-title": "Delete { count, plural, 1 {1 dashboard} other {# dashboards} }", - "delete-dashboards-text": "Be careful, after the confirmation all selected dashboards will be removed and all related data will become unrecoverable.", - "unassign-dashboard-title": "Are you sure you want to unassign the dashboard '{{dashboardTitle}}'?", - "unassign-dashboard-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the customer.", - "unassign-dashboard": "Unassign dashboard", - "unassign-dashboards-title": "Are you sure you want to unassign { count, plural, 1 {1 dashboard} other {# dashboards} }?", - "unassign-dashboards-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the customer.", - "public-dashboard-title": "Dashboard is now public", - "public-dashboard-text": "Your dashboard {{dashboardTitle}} is now public and accessible via next public link:", - "public-dashboard-notice": "Note: Do not forget to make related devices public in order to access their data.", - "make-private-dashboard-title": "Are you sure you want to make the dashboard '{{dashboardTitle}}' private?", - "make-private-dashboard-text": "After the confirmation the dashboard will be made private and won't be accessible by others.", - "make-private-dashboard": "Make dashboard private", - "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", - "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard", - "select-dashboard": "Select dashboard", - "no-dashboards-matching": "No dashboards matching '{{entity}}' were found.", - "dashboard-required": "Dashboard is required.", - "select-existing": "Select existing dashboard", - "create-new": "Create new dashboard", - "new-dashboard-title": "New dashboard title", - "open-dashboard": "Open dashboard", - "set-background": "Set background", - "background-color": "Background color", - "background-image": "Background image", - "background-size-mode": "Background size mode", - "no-image": "No image selected", - "drop-image": "Drop an image or click to select a file to upload.", - "settings": "Settings", - "columns-count": "Columns count", - "columns-count-required": "Columns count is required.", - "min-columns-count-message": "Only 10 minimum column count is allowed.", - "max-columns-count-message": "Only 1000 maximum column count is allowed.", - "widgets-margins": "Margin between widgets", - "horizontal-margin": "Horizontal margin", - "horizontal-margin-required": "Horizontal margin value is required.", - "min-horizontal-margin-message": "Only 0 is allowed as minimum horizontal margin value.", - "max-horizontal-margin-message": "Only 50 is allowed as maximum horizontal margin value.", - "vertical-margin": "Vertical margin", - "vertical-margin-required": "Vertical margin value is required.", - "min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.", - "max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.", - "autofill-height": "Auto fill layout height", - "mobile-layout": "Mobile layout settings", - "mobile-row-height": "Mobile row height, px", + "dashboard": "Panelis", + "dashboards": "Paneļi", + "management": "Paneļu pārvaldība", + "view-dashboards": "Skatīt paneļus", + "add": "Pievienot paneļus", + "assign-dashboard-to-customer": "Piešķirt paneļus klientam", + "assign-dashboard-to-customer-text": "Lūdzu izvēlēties paneļus lai piešķirtu tos klientam", + "assign-to-customer-text": "Lūdzu izvēlēties klientu, kuram piešķirt paneļus", + "assign-to-customer": "Piešķirt klientam", + "unassign-from-customer": "Noņemt no klienta", + "make-public": "Veidot paneli publisku", + "make-private": "Veidot paneli privātu", + "manage-assigned-customers": "Pārvaldīt piešķirtos klientus", + "assigned-customers": "Piešķirtie klienti", + "assign-to-customers": "Piešķirt paneļus klientiem", + "assign-to-customers-text": "Lūdzu atlasīt klientus lai pieškirtu paneļus", + "unassign-from-customers": "Noņemt no klientiem paneļus", + "unassign-from-customers-text": "Lūdzu atlasīt klientus kuriem noņemt paneļus", + "no-dashboards-text": "Nav paneļi atrasti", + "no-widgets": "Nav logrīki konfigurēti", + "add-widget": "Pievienot jaunu logrīku", + "title": "Virsraksts", + "select-widget-title": "Atlasīt logrīku", + "select-widget-subtitle": "Pieejamo logrīku tipu saraksts", + "delete": "Dzēst paneli", + "title-required": "Virsraksts ir nepieciešams.", + "description": "Apraksts", + "details": "Detaļas", + "dashboard-details": "Paneļa detaļas", + "add-dashboard-text": "Pievienot jaunu paneli", + "assign-dashboards": "Pieškirt paneļus", + "assign-new-dashboard": "Pieškirt jaunu paneli", + "assign-dashboards-text": "Pieškirt { count, plural, 1 {1 dashboard} other {# paneļus} } klientiem", + "unassign-dashboards-action-text": "Noņemt { count, plural, 1 {1 dashboard} other {# paneļus} } no klientiem", + "delete-dashboards": "Dzēst paneļus", + "unassign-dashboards": "Noņemt paneļus", + "unassign-dashboards-action-title": "Noņemt { count, plural, 1 {1 dashboard} other {# paneļus} } no klienta", + "delete-dashboard-title": "Vai esat pārliecināts ka vēlaties dzēst paneli '{{dashboardTitle}}'?", + "delete-dashboard-text": "Esiet uzmanīgs, pēc apstiprinājuma panelis un visi tā saistītie dati nebūs atjaunojami.", + "delete-dashboards-title": "Vai esat pārliecināts ka vēlaties dzēst { count, plural, 1 {1 dashboard} other {# paneļus} }?", + "delete-dashboards-action-title": "Dzēst { count, plural, 1 {1 dashboard} other {# paneļus} }", + "delete-dashboards-text": "Esiet uzmanīgs, pēc apstiprinājuma visi izvēlētie paneļi būs noņemti un visi saistitie dati nebūs atjaunojami.", + "unassign-dashboard-title": "Vai esat pārliecināts, ka vēlaties noņemt paneli '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Pēc apstiprinājuma panelis tiks noņemts un nebūs pieejams klientam.", + "unassign-dashboard": "Noņemt paneli", + "unassign-dashboards-title": "Vai esat pārliecināts ka vēlaties noņemt { count, plural, 1 {1 dashboard} other {# paneļus} }?", + "unassign-dashboards-text": "Pēc apstiprinājuma visi izvēlētie paneļi būs noņemti un nebūs pieejami klientam.", + "public-dashboard-title": "Panelis tagad ir publisks", + "public-dashboard-text": "Jūsu panelis {{dashboardTitle}} tagad ir publisks un pieejams pēc saites link:", + "public-dashboard-notice": "Note: Neaizmirstie veidot attiecīgās iekārtas publiski pieejamas lai piekļutu to datiem.", + "make-private-dashboard-title": "Vai esat pārliecināts, ka vēlaties veidot paneli '{{dashboardTitle}}' privātu?", + "make-private-dashboard-text": "Pēc apstiprinājuma panelis būs privāts un nebūs pieejams citiem.", + "make-private-dashboard": "Veidot paneli privātu", + "socialshare-text": "'{{dashboardTitle}}' atbalsts no TeT", + "socialshare-title": "'{{dashboardTitle}}' atbalsts no TeT", + "select-dashboard": "Atlasīt paneli", + "no-dashboards-matching": "Nav atbilstoši paneļi '{{entity}}' atrasti.", + "dashboard-required": "Penelis ir nepieciešams.", + "select-existing": "Atlasīt paneli", + "create-new": "Radīt jaunu paneli", + "new-dashboard-title": "Jauns paneļa Virsraksts", + "open-dashboard": "Atvērt paneli", + "set-background": "Iestatīt fonu", + "background-color": "Fona krāsa", + "background-image": "Fona attēls", + "background-size-mode": "Fona lieluma mode", + "no-image": "Nav izvēlēts attēls", + "drop-image": "Nomest attēlu vai noklikšķiniet lai atlasītu failu augšupielādei.", + "settings": "Iestatījumi", + "columns-count": "Kolonu skaitīšana", + "columns-count-required": "Kolonu skaitīšana ir nepieciešams.", + "min-columns-count-message": "Tikai minimums 10 kolonu skaitīšana ir atļauta.", + "max-columns-count-message": "Tikai maksimums 100 kolonu skaitīšana ir atļauta.", + "widgets-margins": "Robeža starp logrīkiem", + "horizontal-margin": "Horizontālā robeža", + "horizontal-margin-required": "Horizontālās robežas vērtība ir nepieciešama.", + "min-horizontal-margin-message": "Tikai 0 ir atļauta kā minimālā horizontālās robežas vērtība.", + "max-horizontal-margin-message": "Tikai 50 ir atļauta kā maksimālā horizontālās robežas vērtība.", + "vertical-margin": "Vertikālā robeža", + "vertical-margin-required": "Vertikālās robežas vērtība ir nepieciešama.", + "min-vertical-margin-message": "Tikai 0 ir atļauta kā minimālā vertikālās robežas vērtība.", + "max-vertical-margin-message": "Tikai 50 ir atļauta kā maksimālā vertikālās robežas vērtība.", + "autofill-height": "Automātiskās aizpildīšanas izkārtojuma augstums", + "mobile-layout": "Mobilā izkārtojuma iestatījumi", + "mobile-row-height": "Mobilās rindas augstums, px", "mobile-row-height-required": "Mobile row height value is required.", - "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", - "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", - "display-title": "Display dashboard title", - "toolbar-always-open": "Keep toolbar opened", - "title-color": "Title color", - "display-dashboards-selection": "Display dashboards selection", - "display-entities-selection": "Display entities selection", - "display-dashboard-timewindow": "Display timewindow", - "display-dashboard-export": "Display export", - "import": "Import dashboard", - "export": "Export dashboard", - "export-failed-error": "Unable to export dashboard: {{error}}", - "create-new-dashboard": "Create new dashboard", - "dashboard-file": "Dashboard file", - "invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.", - "dashboard-import-missing-aliases-title": "Configure aliases used by imported dashboard", - "create-new-widget": "Create new widget", - "import-widget": "Import widget", - "widget-file": "Widget file", - "invalid-widget-file-error": "Unable to import widget: Invalid widget data structure.", - "widget-import-missing-aliases-title": "Configure aliases used by imported widget", - "open-toolbar": "Open dashboard toolbar", - "close-toolbar": "Close toolbar", - "configuration-error": "Configuration error", - "alias-resolution-error-title": "Dashboard aliases configuration error", - "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.
Please contact your administrator in order to resolve this issue.", - "select-devices": "Select devices", - "assignedToCustomer": "Assigned to customer", - "assignedToCustomers": "Assigned to customers", - "public": "Public", - "public-link": "Public link", - "copy-public-link": "Copy public link", - "public-link-copied-message": "Dashboard public link has been copied to clipboard", - "manage-states": "Manage dashboard states", - "states": "Dashboard states", - "search-states": "Search dashboard states", - "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected", - "edit-state": "Edit dashboard state", - "delete-state": "Delete dashboard state", - "add-state": "Add dashboard state", - "state": "Dashboard state", - "state-name": "Name", - "state-name-required": "Dashboard state name is required.", - "state-id": "State Id", - "state-id-required": "Dashboard state id is required.", - "state-id-exists": "Dashboard state with the same id is already exists.", - "is-root-state": "Root state", - "delete-state-title": "Delete dashboard state", - "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?", - "show-details": "Show details", - "hide-details": "Hide details", - "select-state": "Select target state", - "state-controller": "State controller" + "min-mobile-row-height-message": "Tikai 5 pikseļi ir atļauti kā minimālās mobilās rindas augstuma vērtības.", + "max-mobile-row-height-message": "Tikai 200 pikseļi ir atļauti kā maksimālās mobilās rindas augstuma vērtības.", + "display-title": "Parādīt paneļa virsrakstu", + "toolbar-always-open": "Turēt rīkjoslu atvērtu", + "title-color": "Virsraksta krāsa", + "display-dashboards-selection": "Parādīt paneļa izvēli", + "display-entities-selection": "Parādīt vienību izvēli", + "display-dashboard-timewindow": "Parādīt laika logu", + "display-dashboard-export": "Parādīt eksportu", + "import": "Importēt paneli", + "export": "Eksportēt panelis", + "export-failed-error": "Nav iespējams eksportēt paneli: {{error}}", + "create-new-dashboard": "Radīt jaunu paneli", + "dashboard-file": "Paneļa fails", + "invalid-dashboard-file-error": "Nav iespējams importēt paneli: Invalīda paneļa datu struktūra.", + "dashboard-import-missing-aliases-title": "Jānokonfigurē segvārdi kas lietoti importētajā panelī", + "create-new-widget": "Radīt jaunu logrīku", + "import-widget": "Importēt logrīku", + "widget-file": "Logrīka fails", + "invalid-widget-file-error": "Nav iespējams importēt logrīku: Invalīda logrīka datu struktūra.", + "widget-import-missing-aliases-title": "Jānokonfigurē segvārdi kas lietoti importētajā logrīkā", + "open-toolbar": "Atvērt paneļa rīkjoslu", + "close-toolbar": "Aizvērt rīkjoslu", + "configuration-error": "Konfigurācijas kļūda", + "alias-resolution-error-title": "Paneļa segvārdu konfigurācijas kļūda", + "invalid-aliases-config": "Nav iespējams atrast nevienu iekārtu kam atbilst kāds no segvārdu filtriem.
Lūdzu sazinieties ar savu administrātoru.", + "select-devices": "Atlasīt iekārtas", + "assignedToCustomer": "Pišķirtas klientam", + "assignedToCustomers": "Piešķirtas klientiem", + "public": "Publisks", + "public-link": "Publiska saite", + "copy-public-link": "Kopēt publisku saiti", + "public-link-copied-message": "Paneļa publiskā saite ir kopēta starpliktuvē", + "manage-states": "Pārvaldīt paneļa stāvokļus", + "states": "Paneļa stāvokļi", + "search-states": "Meklēt paneļa stāvokļus", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# paneļu statusus} } atlasītos", + "edit-state": "Rediģēt paneļa stāvokli", + "delete-state": "Dzēst paneļa stāvokli", + "add-state": "Pievienot paneļa stāvokli", + "state": "Paneļa stāvoklis", + "state-name": "Nosaukums", + "state-name-required": "Paneļa stāvokļa nosaukums ir nepieciešams.", + "state-id": "Stāvokļa Id", + "state-id-required": "Paneļa stāvokļa Id ir nepieciešams.", + "state-id-exists": "Paneļa stāvoklis ar šādu Id jau eksistē.", + "is-root-state": "Saknes stāvoklis", + "delete-state-title": "Dzēst paneļa stāvokli", + "delete-state-text": "Vai esat pārliecināts ka vēlaties dzēst paneļa stāvokli ar nosaukumu '{{stateName}}'?", + "show-details": "Rādīt detaļas", + "hide-details": "Noslēpt detaļas", + "select-state": "Atlasīt mērķa stāvokli", + "state-controller": "Stavokļa kontrolieris" }, "datakey": { - "settings": "Settings", - "advanced": "Advanced", - "label": "Label", - "color": "Color", - "units": "Special symbol to show next to value", - "decimals": "Number of digits after floating point", - "data-generation-func": "Data generation function", - "use-data-post-processing-func": "Use data post-processing function", - "configuration": "Data key configuration", - "timeseries": "Timeseries", - "attributes": "Attributes", - "alarm": "Alarm fields", - "timeseries-required": "Entity timeseries are required.", - "timeseries-or-attributes-required": "Entity timeseries/attributes are required.", - "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", - "alarm-fields-required": "Alarm fields are required.", - "function-types": "Function types", - "function-types-required": "Function types are required.", - "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", - "time-description": "timestamp of the current value;", - "value-description": "the current value;", - "prev-value-description": "result of the previous function call;", - "time-prev-description": "timestamp of the previous value;", - "prev-orig-value-description": "original previous value;" + "settings": "Iestatījumi", + "advanced": "Pieredzējis lietotājs", + "label": "Etiķete", + "color": "Krāsa", + "units": "Speciāls simbols, ko parādīt pēc vērtība", + "decimals": "Ciparu skaits aiz komata", + "data-generation-func": "Datu ģenerācijas funkcija", + "use-data-post-processing-func": "Lietot datu pēcapstrādes funkciju", + "configuration": "Datu atslēgu konfigurācija", + "timeseries": "Laika periodi", + "attributes": "Atribūti", + "alarm": "Trauksme", + "timeseries-required": "Vienības laika periodi ir nepieciešami.", + "timeseries-or-attributes-required": "Vienības laika periodi/atribūti ir nepieciešami.", + "maximum-timeseries-or-attributes": "Maksimums { count, plural, 1 {1 timeseries/attribute is allowed.} other {# laika sērijas/atribūti ir atļauti} }", + "alarm-fields-required": "Trauksmes lauki ir nepieciešami.", + "function-types": "Funkciju tipi", + "function-types-required": "Funkciju tipi ir nepieciešami.", + "maximum-function-types": "Maksimums { count, plural, 1 {1 function type is allowed.} other {# funkciju tipi ir atļauti} }", + "time-description": "Laika zīmogs patreizējai vērtībai;", + "value-description": "patreizējā vērtība;", + "prev-value-description": "rezultāts no iepriekšējā funkciju pieprasījuma;", + "time-prev-description": "Laika zīmogs no iepriekšējās vērtības;", + "prev-orig-value-description": "Oriģinālā iepriekšējā vērtība;" }, "datasource": { - "type": "Datasource type", - "name": "Name", - "add-datasource-prompt": "Please add datasource" + "type": "Datu avota tips", + "name": "Nosaukums", + "add-datasource-prompt": "Lūdzu pievienot datu avotu" }, "details": { - "edit-mode": "Edit mode", - "toggle-edit-mode": "Toggle edit mode" + "edit-mode": "Rediģēšanas mode", + "toggle-edit-mode": "Pārslēgt rediģēšanas modi" }, "device": { - "device": "Device", - "device-required": "Device is required.", - "devices": "Devices", - "management": "Device management", - "view-devices": "View Devices", - "device-alias": "Device alias", - "aliases": "Device aliases", - "no-alias-matching": "'{{alias}}' not found.", - "no-aliases-found": "No aliases found.", - "no-key-matching": "'{{key}}' not found.", - "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}}'.
Device aliases must be unique whithin the dashboard.", - "configure-alias": "Configure '{{alias}}' alias", - "no-devices-matching": "No devices matching '{{entity}}' were found.", - "alias": "Alias", - "alias-required": "Device alias is required.", - "remove-alias": "Remove device alias", - "add-alias": "Add device alias", - "name-starts-with": "Device name starts with", - "device-list": "Device list", - "use-device-name-filter": "Use filter", - "device-list-empty": "No devices selected.", - "device-name-filter-required": "Device name filter is required.", - "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.", - "add": "Add Device", - "assign-to-customer": "Assign to customer", - "assign-device-to-customer": "Assign Device(s) To Customer", - "assign-device-to-customer-text": "Please select the devices to assign to the customer", - "make-public": "Make device public", - "make-private": "Make device private", - "no-devices-text": "No devices found", - "assign-to-customer-text": "Please select the customer to assign the device(s)", - "device-details": "Device details", - "add-device-text": "Add new device", - "credentials": "Credentials", - "manage-credentials": "Manage credentials", - "delete": "Delete device", - "assign-devices": "Assign devices", - "assign-devices-text": "Assign { count, plural, 1 {1 device} other {# devices} } to customer", - "delete-devices": "Delete devices", - "unassign-from-customer": "Unassign from customer", - "unassign-devices": "Unassign devices", - "unassign-devices-action-title": "Unassign { count, plural, 1 {1 device} other {# devices} } from customer", - "assign-new-device": "Assign new device", - "make-public-device-title": "Are you sure you want to make the device '{{deviceName}}' public?", - "make-public-device-text": "After the confirmation the device and all its data will be made public and accessible by others.", - "make-private-device-title": "Are you sure you want to make the device '{{deviceName}}' private?", - "make-private-device-text": "After the confirmation the device and all its data will be made private and won't be accessible by others.", - "view-credentials": "View credentials", - "delete-device-title": "Are you sure you want to delete the device '{{deviceName}}'?", - "delete-device-text": "Be careful, after the confirmation the device and all related data will become unrecoverable.", - "delete-devices-title": "Are you sure you want to delete { count, plural, 1 {1 device} other {# devices} }?", - "delete-devices-action-title": "Delete { count, plural, 1 {1 device} other {# devices} }", - "delete-devices-text": "Be careful, after the confirmation all selected devices will be removed and all related data will become unrecoverable.", - "unassign-device-title": "Are you sure you want to unassign the device '{{deviceName}}'?", - "unassign-device-text": "After the confirmation the device will be unassigned and won't be accessible by the customer.", - "unassign-device": "Unassign device", - "unassign-devices-title": "Are you sure you want to unassign { count, plural, 1 {1 device} other {# devices} }?", - "unassign-devices-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the customer.", - "device-credentials": "Device Credentials", - "credentials-type": "Credentials type", - "access-token": "Access token", - "access-token-required": "Access token is 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 is required.", - "secret": "Secret", - "secret-required": "Secret is required.", - "device-type": "Device type", - "device-type-required": "Device type is required.", - "select-device-type": "Select device type", - "enter-device-type": "Enter device type", - "any-device": "Any device", - "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", - "device-type-list-empty": "No device types selected.", - "device-types": "Device types", - "name": "Name", - "name-required": "Name is required.", - "description": "Description", - "label": "Label", - "events": "Events", - "details": "Details", - "copyId": "Copy device Id", - "copyAccessToken": "Copy access token", - "idCopiedMessage": "Device Id has been copied to clipboard", - "accessTokenCopiedMessage": "Device access token has been copied to clipboard", - "assignedToCustomer": "Assigned to customer", - "unable-delete-device-alias-title": "Unable to delete device alias", - "unable-delete-device-alias-text": "Device alias '{{deviceAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", - "is-gateway": "Is gateway", - "public": "Public", - "device-public": "Device is public", - "select-device": "Select device", - "import": "Import device", - "device-file": "Device file" + "device": "Iekārta", + "device-required": "Iekārta ir nepieciešama.", + "devices": "Iekārtas", + "management": "Iekārtu pārvaldība", + "view-devices": "Skatīt iekārtas", + "device-alias": "Iekārtu segvārdi", + "aliases": "Iekārtas segvārdi", + "no-alias-matching": "'{{alias}}' nav atrasti.", + "no-aliases-found": "Nav segvārdi atrasti.", + "no-key-matching": "'{{key}}' nav atrasti.", + "no-keys-found": "Nav atslēgas atrastas.", + "create-new-alias": "Radīt jaunu!", + "create-new-key": "Radīt jaunu!", + "duplicate-alias-error": "Dublēti segvārdi atrasti '{{alias}}'.
Iekārtas segvārdiem ir jābūt unikāliem panelī.", + "configure-alias": "Konfigurēt '{{alias}}' segvārdus", + "no-devices-matching": "Nav iekārtu atbilstības '{{entity}}' atrastas.", + "alias": "Segvārdi", + "alias-required": "Iekārtu segvārdi ir nepieciešami.", + "remove-alias": "Noņemt iekārtas segvārdus", + "add-alias": "Pievienot iekārtas segvārdus", + "name-starts-with": "Iekārtas nosaukums sākas ar", + "device-list": "Iekārtu saraksts", + "use-device-name-filter": "Lietot filtru", + "device-list-empty": "Nav iekārtas atlasītas.", + "device-name-filter-required": "Iekārtas nosaukuma filtrs ir nepieciešams.", + "device-name-filter-no-device-matched": "Nav iekārtas kas sākas ar '{{device}}' atrastas.", + "add": "Pievienot iekārtu", + "assign-to-customer": "Piešķirt klientam", + "assign-device-to-customer": "Piešķirt iekārtas klientam", + "assign-device-to-customer-text": "Lūdzu atlasīt iekārtas lai pieškirtu klientam", + "make-public": "Veidot iekārtu publisku", + "make-private": "Veidot iekārtu privātu", + "no-devices-text": "Nav iekārtas atrastas", + "assign-to-customer-text": "Lūdzu atlasīt klientu lai pieškirtu iekārtas", + "device-details": "iekārtas detaļas", + "add-device-text": "Pievienot jaunu iekārtu", + "credentials": "Akreditācijas dati", + "manage-credentials": "Pārvaldīt akreditācijas datus", + "delete": "Dzēst iekārtu", + "assign-devices": "Pieškirt iekārtas", + "assign-devices-text": "Piešķirt { count, plural, 1 {1 device} other {# iekārtas} } klientam", + "delete-devices": "Dzēst iekārtas", + "unassign-from-customer": "Noņemt no klienta", + "unassign-devices": "Noņemt iekārtas", + "unassign-devices-action-title": "Noņemt { count, plural, 1 {1 device} other {# iekārtas} } no klienta", + "assign-new-device": "Pieškirt jaunu iekārtu", + "make-public-device-title": "Vai esat pārliecināts ka vēlaties veidot iekārtu '{{deviceName}}' publisku?", + "make-public-device-text": "Pēc apstiprinājuma iekārta un tās saistītie dati būs pieejami publiski un pieejami citiem.", + "make-private-device-title": "vai esat pārliecināts ka vēlaties veidot iekārtu '{{deviceName}}' privāti?", + "make-private-device-text": "Pēc apstiprinājuma iekārta un tās saistītie dati būs pieejami privāti un nebūs pieejami citiem.", + "view-credentials": "Skatīt akreditācijas datus", + "delete-device-title": "Vai esat pārliecināts, ka vēlaties dzēst iekārtu '{{deviceName}}'?", + "delete-device-text": "Esat uzmanīgs, pēc apstiprinājuma iekārta un tās saistītie dati nebūs atjaunojami.", + "delete-devices-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 device} other {# iekārtas} }?", + "delete-devices-action-title": "Dzēst { count, plural, 1 {1 device} other {# iekārtas} }", + "delete-devices-text": "Esat uzmanīgs, pēc apstiprinājuma iekārtas un to saistītie dati tiks noņemti un nebūs atjaunojami.", + "unassign-device-title": "Vai esat pārliecināts, ka vēlaties noņemt iekārtu '{{deviceName}}'?", + "unassign-device-text": "Pēc apstiprinājuma iekārta tiks noņemta un nebūs pieejama klientam.", + "unassign-device": "Noņemt iekārtu", + "unassign-devices-title": "Vai esat pārliecināts, ka vēlaties noņemt { count, plural, 1 {1 device} other {# iekārtas} }?", + "unassign-devices-text": "Pēc apstipinājuma visas atlasītās iekārtas būs noņemtas un nebūs pieejamas klientam.", + "device-credentials": "iekārtas akreditācijas dati", + "credentials-type": "Akreditācijas datu tips", + "access-token": "Piekļuves tokens", + "access-token-required": "Piekļuves tokens ir nepieciešams.", + "access-token-invalid": "Piekļuves tokena garumam ir jābūt no 1 līdz 20 rakstzīmēm.", + "rsa-key": "RSA publiskā atslēga", + "rsa-key-required": "RSA publiskā atslēga ir nepieciešama.", + "secret": "Noslēpums", + "secret-required": "Noslēpums ir nepieciešams.", + "device-type": "Iekārtas tips", + "device-type-required": "Iekārtas tips ir nepieciešams.", + "select-device-type": "Atlasīt iekārtas tipu", + "enter-device-type": "Ievadīt iekārtas tipu", + "any-device": "Jebkura iekārta", + "no-device-types-matching": "Nav iekārtas tipa saderības '{{entitySubtype}}' atrastas.", + "device-type-list-empty": "Nav iekārtas tipi izvēlēti.", + "device-types": "Iekārtas tipi", + "name": "Nosaukums", + "name-required": "Nosaukums ir nepieciešams.", + "description": "Apraksts", + "label": "Etiķete", + "events": "Notikumi", + "details": "Detaļas", + "copyId": "Kopēt iekārtas Id", + "copyAccessToken": "Kopēt piekļuves tokenu", + "idCopiedMessage": "iekārtas Id ir kopēts uz starpliktuvi", + "accessTokenCopiedMessage": "Iekārtas piekļuves tokens ir kopēts uz starpliktuvi", + "assignedToCustomer": "Piešķirts klientam", + "unable-delete-device-alias-title": "Nav iespējas dzēst iekārtas segvārdus", + "unable-delete-device-alias-text": "Iekārtas segvārdi '{{deviceAlias}}' nevar būt dzēsti, jo tie lietoti sekojošajos logrīkos:
{{widgetsList}}", + "is-gateway": "Tā ir vārteja", + "public": "Publisks", + "device-public": "Iekārta ir publiska", + "select-device": "Atlasīt iekārtu", + "import": "Importēt iekārtu", + "device-file": "Iekārtas fails" }, "dialog": { - "close": "Close dialog" + "close": "Aizvērt dialogu" }, "direction": { - "column": "Column", - "row": "Row" + "column": "Kolona", + "row": "Rinda" }, "error": { - "unable-to-connect": "Unable to connect to the server! Please check your internet connection.", - "unhandled-error-code": "Unhandled error code: {{errorCode}}", - "unknown-error": "Unknown error" + "unable-to-connect": "Nav iespējams pievienoties serverim! Lūdzu pārbaudīt interneta savienojumu.", + "unhandled-error-code": "Neapstrādāta kļūda: {{errorCode}}", + "unknown-error": "Nezināma kļūda" }, "entity": { - "entity": "Entity", - "entities": "Entities", - "aliases": "Entity aliases", - "entity-alias": "Entity alias", - "unable-delete-entity-alias-title": "Unable to delete entity alias", - "unable-delete-entity-alias-text": "Entity alias '{{entityAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", - "duplicate-alias-error": "Duplicate alias found '{{alias}}'.
Entity aliases must be unique whithin the dashboard.", - "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.", - "configure-alias": "Configure '{{alias}}' alias", - "alias": "Alias", - "alias-required": "Entity alias is required.", - "remove-alias": "Remove entity alias", - "add-alias": "Add entity alias", - "entity-list": "Entity list", - "entity-type": "Entity type", - "entity-types": "Entity types", - "entity-type-list": "Entity type list", - "any-entity": "Any entity", - "enter-entity-type": "Enter entity type", - "no-entities-matching": "No entities matching '{{entity}}' were found.", - "no-entity-types-matching": "No entity types matching '{{entityType}}' were found.", - "name-starts-with": "Name starts with", - "use-entity-name-filter": "Use filter", - "entity-list-empty": "No entities selected.", - "entity-type-list-empty": "No entity types selected.", - "entity-name-filter-required": "Entity name filter is required.", - "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.", - "all-subtypes": "All", - "select-entities": "Select entities", - "no-aliases-found": "No aliases found.", - "no-alias-matching": "'{{alias}}' not found.", - "create-new-alias": "Create a new one!", - "key": "Key", - "key-name": "Key name", - "no-keys-found": "No keys found.", - "no-key-matching": "'{{key}}' not found.", - "create-new-key": "Create a new one!", - "type": "Type", - "type-required": "Entity type is required.", - "type-device": "Device", - "type-devices": "Devices", - "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", - "device-name-starts-with": "Devices whose names start 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 whose names start with '{{prefix}}'", - "type-entity-view": "Entity View", - "type-entity-views": "Entity Views", - "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", - "entity-view-name-starts-with": "Entity Views whose names start with '{{prefix}}'", - "type-rule": "Rule", - "type-rules": "Rules", - "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", - "rule-name-starts-with": "Rules whose names start with '{{prefix}}'", - "type-plugin": "Plugin", - "type-plugins": "Plugins", - "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", - "plugin-name-starts-with": "Plugins whose names start with '{{prefix}}'", - "type-tenant": "Tenant", - "type-tenants": "Tenants", - "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", - "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'", - "type-customer": "Customer", - "type-customers": "Customers", - "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", - "customer-name-starts-with": "Customers whose names start with '{{prefix}}'", - "type-user": "User", - "type-users": "Users", - "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", - "user-name-starts-with": "Users whose names start 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 whose names start with '{{prefix}}'", - "type-alarm": "Alarm", - "type-alarms": "Alarms", - "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", - "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'", - "type-rulechain": "Rule chain", - "type-rulechains": "Rule chains", - "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", - "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'", - "type-rulenode": "Rule node", - "type-rulenodes": "Rule nodes", - "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", - "rulenode-name-starts-with": "Rule nodes whose names start with '{{prefix}}'", - "type-current-customer": "Current Customer", - "search": "Search entities", - "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selected", - "entity-name": "Entity name", - "details": "Entity details", - "no-entities-prompt": "No entities found", - "no-data": "No data to display", - "columns-to-display": "Columns to Display" + "entity": "Vienība", + "entities": "Vienības", + "aliases": "Vienību segvārdi", + "entity-alias": "Vienību segvārdi", + "unable-delete-entity-alias-title": "Nav iespējams dzēst vienību segvārdus", + "unable-delete-entity-alias-text": "Vienību segvārdi '{{entityAlias}}' nevar tikt dzēsti, jo tos izmanto sekojošie logrīki:
{{widgetsList}}", + "duplicate-alias-error": "Dublikāti segvārdi atrasti '{{alias}}'.
Vienību segvārdiem ir jābūt unikāliem paneļos.", + "missing-entity-filter-error": "Filtrs trūkst priekš segvārda '{{alias}}'.", + "configure-alias": "Konfigurēt '{{alias}}' segvārdus", + "alias": "Segvārds", + "alias-required": "Vienību segvārds ir nepieciešams.", + "remove-alias": "Noņemt vienību segvārdu", + "add-alias": "Pievienot vienību segvārdu", + "entity-list": "Vienību saraksts", + "entity-type": "Vienības tips", + "entity-types": "Vienības tipi", + "entity-type-list": "Vienības tipu saraksts", + "any-entity": "Jebkura vienība", + "enter-entity-type": "Ievadīt vienības tipu", + "no-entities-matching": "Nav vienības saderības '{{entity}}' atrastas.", + "no-entity-types-matching": "Nav vienības tipu saderības '{{entityType}}' atrastas.", + "name-starts-with": "Nosaukums sākas ar", + "use-entity-name-filter": "Lietot filtru", + "entity-list-empty": "Nav vienības atlasītas.", + "entity-type-list-empty": "Nav vienības tipi atlasīti.", + "entity-name-filter-required": "Vienību nosaukuma filtri ir vajadzīgi.", + "entity-name-filter-no-entity-matched": "Nav vienības kas sākas ar '{{entity}}' atrastas.", + "all-subtypes": "Visi", + "select-entities": "Atlasīt vienības", + "no-aliases-found": "Nav segvārdi atrasti.", + "no-alias-matching": "'{{alias}}' nav atrasts.", + "create-new-alias": "Radīt jaunu!", + "key": "Atslēga", + "key-name": "Atslēgas nosaukums", + "no-keys-found": "Nav atslēgas atrastas.", + "no-key-matching": "'{{key}}' nav atrasta.", + "create-new-key": "Radīt jaunu!", + "type": "Tips", + "type-required": "Vienības tips ir nepieciešams.", + "type-device": "Iekārta", + "type-devices": "Iekārtas", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # iekārtas} }", + "device-name-starts-with": "Iekārtas, kuras nosaukumi sākas ar '{{prefix}}'", + "type-asset": "Aktīvs", + "type-assets": "Aktīvi", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # aktīvi} }", + "asset-name-starts-with": "Aktīvi, kuru nosaukumi sākas ar '{{prefix}}'", + "type-entity-view": "Vienības skats View", + "type-entity-views": "Vienības skati", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # vienību skati} }", + "entity-view-name-starts-with": "Vienibas skati, kuru nosaukumi sākas ar '{{prefix}}'", + "type-rule": "Noteikums", + "type-rules": "Noteikumi", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # noteikumi} }", + "rule-name-starts-with": "Noteikumi, kuru nosaukumi sākas ar '{{prefix}}'", + "type-plugin": "Spraudnis", + "type-plugins": "Spraudņi", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # spraudņi} }", + "plugin-name-starts-with": "Spraudņi, kuru vārds sākas ar '{{prefix}}'", + "type-tenant": "Īrnieks", + "type-tenants": "Īrnieki", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # īrnieki} }", + "tenant-name-starts-with": "Īrnieki, kuru nosaukumi sākas ar '{{prefix}}'", + "type-customer": "Klients", + "type-customers": "Klienti", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # klienti} }", + "customer-name-starts-with": "Klienti, kuru nosaukumi sākas ar '{{prefix}}'", + "type-user": "Lietotājs", + "type-users": "Lietotāji", + "list-of-users": "{ count, plural, 1 {One user} other {List of # lietotāji} }", + "user-name-starts-with": "Lietotāji, kuru nosaukums sākas ar '{{prefix}}'", + "type-dashboard": "Panelis", + "type-dashboards": "Paneļi", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # paneļi} }", + "dashboard-name-starts-with": "Paneļi, kuru nosaukums sākas ar '{{prefix}}'", + "type-alarm": "Trauksme", + "type-alarms": "Trauksmes", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # trauksmes} }", + "alarm-name-starts-with": "Trauksmes, kuru nosaukumi sākas ar '{{prefix}}'", + "type-rulechain": "Noteikumu ķēde", + "type-rulechains": "Noteikumu ķēdes", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # noteikumu ķēdes} }", + "rulechain-name-starts-with": "Noteikumu ķēdes, kuru nosaukumi sākas ar '{{prefix}}'", + "type-rulenode": "Noteikumu node", + "type-rulenodes": "Noteikumu nodes", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # noteikumu nodes} }", + "rulenode-name-starts-with": "Noteikumu nodes, juru nosaukumi sākas ar '{{prefix}}'", + "type-current-customer": "Pašreizējais klients", + "search": "Meklēšanas vienības", + "selected-entities": "{ count, plural, 1 {1 entity} other {# vienības} } atlasītas", + "entity-name": "Vienības nosaukums", + "details": "Vienības detaļas", + "no-entities-prompt": "Nav vienības atrastas", + "no-data": "Nav datu ko attēlot", + "columns-to-display": "Kolonas ko attēlot" }, "entity-view": { - "entity-view": "Entity View", - "entity-view-required": "Entity view is required.", - "entity-views": "Entity Views", - "management": "Entity View management", - "view-entity-views": "View Entity Views", - "entity-view-alias": "Entity View alias", - "aliases": "Entity View aliases", - "no-alias-matching": "'{{alias}}' not found.", - "no-aliases-found": "No aliases found.", - "no-key-matching": "'{{key}}' not found.", - "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 within the dashboard.", - "configure-alias": "Configure '{{alias}}' alias", - "no-entity-views-matching": "No entity views matching '{{entity}}' were found.", - "alias": "Alias", - "alias-required": "Entity View alias is required.", - "remove-alias": "Remove entity view alias", - "add-alias": "Add entity view alias", - "name-starts-with": "Entity View name starts with", - "entity-view-list": "Entity View list", - "use-entity-view-name-filter": "Use filter", - "entity-view-list-empty": "No entity views selected.", - "entity-view-name-filter-required": "Entity view name filter is required.", - "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", - "add": "Add Entity View", - "assign-to-customer": "Assign to customer", - "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", - "assign-entity-view-to-customer-text": "Please select the entity views to assign to the customer", - "no-entity-views-text": "No entity views found", - "assign-to-customer-text": "Please select the customer to assign the entity view(s)", - "entity-view-details": "Entity view details", - "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 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 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 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 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.", - "select-entity-view-type": "Select entity view type", - "enter-entity-view-type": "Enter entity view type", - "any-entity-view": "Any entity view", - "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", - "entity-view-type-list-empty": "No entity view types selected.", - "entity-view-types": "Entity View types", - "name": "Name", - "name-required": "Name is required.", - "description": "Description", - "events": "Events", - "details": "Details", - "copyId": "Copy entity view Id", - "assignedToCustomer": "Assigned to customer", - "unable-entity-view-device-alias-title": "Unable to delete entity view alias", - "unable-entity-view-device-alias-text": "Device alias '{{entityViewAlias}}' can't be deleted as it used by the following widget(s):
{{widgetsList}}", - "select-entity-view": "Select entity view", - "make-public": "Make entity view public", - "make-private": "Make entity view private", - "start-date": "Start date", - "start-ts": "Start time", - "end-date": "End date", - "end-ts": "End time", - "date-limits": "Date limits", - "client-attributes": "Client attributes", - "shared-attributes": "Shared attributes", - "server-attributes": "Server attributes", - "timeseries": "Timeseries", - "client-attributes-placeholder": "Client attributes", - "shared-attributes-placeholder": "Shared attributes", - "server-attributes-placeholder": "Server attributes", - "timeseries-placeholder": "Timeseries", - "target-entity": "Target entity", - "attributes-propagation": "Attributes propagation", - "attributes-propagation-hint": "Entity View will automatically copy specified attributes from Target Entity each time you save or update this entity view. For performance reasons target entity attributes are not propagated to entity view on each attribute change. You can enable automatic propagation by configuring \"copy to view\" rule node in your rule chain and linking \"Post attributes\" and \"Attributes Updated\" messages to the new rule node.", - "timeseries-data": "Timeseries data", + "entity-view": "Vienības skats", + "entity-view-required": "Vienības skats ir nepieciešams.", + "entity-views": "Vienības skati", + "management": "Vienības skatu pārvaldība", + "view-entity-views": "Skatīt vienību skatus", + "entity-view-alias": "Vienību skatu segvārdi", + "aliases": "Vienību skatu segvārdi", + "no-alias-matching": "'{{alias}}' nav atrasts.", + "no-aliases-found": "Nav segvārds atrasts.", + "no-key-matching": "'{{key}}' nav atrasts.", + "no-keys-found": "Nav atslēgas atrastas.", + "create-new-alias": "Radīt jaunu!", + "create-new-key": "Radīt jaunu!", + "duplicate-alias-error": "Dublēti segvārdi atrasti '{{alias}}'.
Vienību skatu segvārdiem ir jābūt unikāliem paneļa ietvaros.", + "configure-alias": "Konfigurēt '{{alias}}' segvārdu", + "no-entity-views-matching": "Nav vienību skata atbilstības '{{entity}}' atrastas.", + "alias": "Segvārds", + "alias-required": "Vienību skatu segvārdi ir nepieciešami.", + "remove-alias": "Noņemt vienību skatu segvārdu", + "add-alias": "Pievienot vienību skatu segvārdu", + "name-starts-with": "Vienību skata nosaukums sākas ar", + "entity-view-list": "Vienību skata saraksts", + "use-entity-view-name-filter": "Lietot filtru", + "entity-view-list-empty": "Nav vienību skati atlasīti.", + "entity-view-name-filter-required": "Vienību skatu nosaukumu filtri ir nepieciešami.", + "entity-view-name-filter-no-entity-view-matched": "Nav vienību skati kas sākas ar '{{entityView}}' atrasti.", + "add": "Pievienot vienību skatu", + "assign-to-customer": "Pieškirt klientam", + "assign-entity-view-to-customer": "Piešķirt vienību skatus klientam", + "assign-entity-view-to-customer-text": "Lūdzu izvēlēties vienību skatus ko pieškirt klientam", + "no-entity-views-text": "Nav vienību skati atrasti", + "assign-to-customer-text": "Lūdzu izvēlēties klientu lai pieškirtu vienības skatus", + "entity-view-details": "Vienību skata detaļas", + "add-entity-view-text": "Pievienot jaunu vienību skatu", + "delete": "Dzēsts vienību skatu", + "assign-entity-views": "Piešķirt vienību skatus", + "assign-entity-views-text": "Piešķirt { count, plural, 1 {1 entityView} other {# vienību skati} } klientam", + "delete-entity-views": "Dzēst vienību skatus", + "unassign-from-customer": "Noņemt no klienta", + "unassign-entity-views": "Noņemt vienību skatus", + "unassign-entity-views-action-title": "Noņemt { count, plural, 1 {1 entityView} other {# vienību skati} } no klienta", + "assign-new-entity-view": "Piešķirt jaunu vienību skatu", + "delete-entity-view-title": "Vai esat pārliecināts,ka vēlaties dzēst vienību skatu '{{entityViewName}}'?", + "delete-entity-view-text": "Esiet uzmanīgs, pēc apstiprinājuma vienību skats un tā sasitītie dati nebūs atjaunojami.", + "delete-entity-views-title": "Vai esat pārliecināts, ka vēlaties vienību skatu { count, plural, 1 {1 entityView} other {# vienību skati} }?", + "delete-entity-views-action-title": "Dzēst { count, plural, 1 {1 entityView} other {# vienību skati} }", + "delete-entity-views-text": "Esiet uzmanīgs, pēc apstiprinājuma visir atlasītie vienības skati tiks noņemti un to saistītie dati nebūs atjaunojami.", + "unassign-entity-view-title": "Vai esat pārliecināts, ka vēlaties atspējot vienību skatu '{{entityViewName}}'?", + "unassign-entity-view-text": "Pēc apstiprinājuma vienību skats tiks atspējots un nebūs pieejams klientam.", + "unassign-entity-view": "Atspējot vienību skatu", + "unassign-entity-views-title": "Vai esat pārliecināts, ka vēlaties atspējot { count, plural, 1 {1 entityView} other {# vienību skatus} }?", + "unassign-entity-views-text": "Pēc apstiprinājuma visi atlasītie vienību skati būs atspējoti un nebūs pieejami klientiem.", + "entity-view-type": "Vienību skata tips", + "entity-view-type-required": "Vienību skata tips ir nepieciešams.", + "select-entity-view-type": "Atlasīt vienību skata tipu", + "enter-entity-view-type": "Ievadīt vienību skata tipu", + "any-entity-view": "Jebkurš vienību skats", + "no-entity-view-types-matching": "Nav vienību skata atbilstības '{{entitySubtype}}' atrastas.", + "entity-view-type-list-empty": "Nav vienību skatu tipi atlasīti.", + "entity-view-types": "Vienību skatu tipi", + "name": "Nosaukums", + "name-required": "Nosaukums ir nepieciešams.", + "description": "Apraksts", + "events": "Notikumi", + "details": "Detaļas", + "copyId": "Kopēt vienību skata Id", + "assignedToCustomer": "Piešķirta klientam", + "unable-entity-view-device-alias-title": "Nav iespējas dzēst vienību skata segvārdu", + "unable-entity-view-device-alias-text": "Iekārtas segvārds '{{entityViewAlias}}' nevar tikt dzēsts, jo to izmanto sekojošs logrīks:
{{widgetsList}}", + "select-entity-view": "Atlasīt vienību skatu", + "make-public": "Veidot vienību skatu publisku", + "make-private": "Veidot vienību skatu privātu", + "start-date": "Starta datums", + "start-ts": "Starta laiks", + "end-date": "Beigu datums", + "end-ts": "Beigu laiks", + "date-limits": "Datuma limits", + "client-attributes": "Klienta atribūti", + "shared-attributes": "Dalītie atribūti", + "server-attributes": "Servera atribūti", + "timeseries": "Laika sērijas", + "client-attributes-placeholder": "Klienta atribūti", + "shared-attributes-placeholder": "Dalītie atribūti", + "server-attributes-placeholder": "Servera atribūti", + "timeseries-placeholder": "Laika sērijas", + "target-entity": "Mērķa vienība", + "attributes-propagation": "Atribūtu izplatīšana", + "attributes-propagation-hint": "Vienību skats automātiski kopē specificētos atribūtus no mērķa vienības katru reizi kad jūs saglabājat vai atjaunojat vienību skatu.", + "timeseries-data": "Laika sērijas dati", "timeseries-data-hint": "Configure timeseries data keys of the target entity that will be accessible to the entity view. This timeseries data is read-only.", - "make-public-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' public?", - "make-public-entity-view-text": "After the confirmation the entity view and all its data will be made public and accessible by others.", - "make-private-entity-view-title": "Are you sure you want to make the entity view '{{entityViewName}}' private?", - "make-private-entity-view-text": "After the confirmation the entity view and all its data will be made private and won't be accessible by others." + "make-public-entity-view-title": "Vai esat pārliecināts, ka vēlaties veidot vienību skatu '{{entityViewName}}' publisku?", + "make-public-entity-view-text": "Pēc apstiprinājuma vienību skats un tā saistītie dati būs publiski un pieejami citiem.", + "make-private-entity-view-title": "Vai esat pārliecināts, ka vēlaties veidot vienību skatu '{{entityViewName}}' privātu?", + "make-private-entity-view-text": "Pēc apstiprinājuma vienību skats un tā saistītie dati būs privāti un nebūs pieejami citiem." }, "event": { - "event-type": "Event type", - "type-error": "Error", - "type-lc-event": "Lifecycle event", - "type-stats": "Statistics", - "type-debug-rule-node": "Debug", - "type-debug-rule-chain": "Debug", - "no-events-prompt": "No events found", - "error": "Error", - "alarm": "Alarm", - "event-time": "Event time", - "server": "Server", - "body": "Body", - "method": "Method", - "type": "Type", - "entity": "Entity", - "message-id": "Message Id", - "message-type": "Message Type", - "data-type": "Data Type", - "relation-type": "Relation Type", + "event-type": "Notikuma tips", + "type-error": "Kļūda", + "type-lc-event": "Dzīves cikla notikums", + "type-stats": "Statistika", + "type-debug-rule-node": "Atkļūdot", + "type-debug-rule-chain": "Atkļūdot", + "no-events-prompt": "Nav notikumi atrasti", + "error": "Kļūda", + "alarm": "Trauksme", + "event-time": "Notikuma laiks", + "server": "Serveris", + "body": "Galvenā daļa", + "method": "Metode", + "type": "Tips", + "entity": "Vienība", + "message-id": "Ziņojuma Id", + "message-type": "Ziņojuma tips", + "data-type": "Datu tips", + "relation-type": "Attiecību tips", "metadata": "Metadata", - "data": "Data", - "event": "Event", - "status": "Status", - "success": "Success", - "failed": "Failed", - "messages-processed": "Messages processed", - "errors-occurred": "Errors occurred" + "data": "Dati", + "event": "Notikumi", + "status": "Statuss", + "success": "Sekmīgi", + "failed": "Kļūda", + "messages-processed": "Ziņojumi apstrādāti", + "errors-occurred": "Kļūdas konstatētas" }, "extension": { - "extensions": "Extensions", - "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selected", - "type": "Type", - "key": "Key", - "value": "Value", + "extensions": "Paplašinājumi", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# paplašinājumi} } atlasītie", + "type": "Tips", + "key": "Atslēga", + "value": "Vērtība", "id": "Id", - "extension-id": "Extension id", - "extension-type": "Extension type", + "extension-id": "Paplašinājuma id", + "extension-type": "Paplašinājuma tips", "transformer-json": "JSON *", - "unique-id-required": "Current extension id already exists.", - "delete": "Delete extension", - "add": "Add extension", - "edit": "Edit extension", - "delete-extension-title": "Are you sure you want to delete the extension '{{extensionId}}'?", - "delete-extension-text": "Be careful, after the confirmation the extension and all related data will become unrecoverable.", - "delete-extensions-title": "Are you sure you want to delete { count, plural, 1 {1 extension} other {# extensions} }?", - "delete-extensions-text": "Be careful, after the confirmation all selected extensions will be removed.", - "converters": "Converters", - "converter-id": "Converter id", - "configuration": "Configuration", - "converter-configurations": "Converter configurations", - "token": "Security token", - "add-converter": "Add converter", - "add-config": "Add converter configuration", - "device-name-expression": "Device name expression", - "device-type-expression": "Device type expression", - "custom": "Custom", - "to-double": "To Double", - "transformer": "Transformer", - "json-required": "Transformer json is required.", - "json-parse": "Unable to parse transformer json.", - "attributes": "Attributes", - "add-attribute": "Add attribute", - "add-map": "Add mapping element", - "timeseries": "Timeseries", - "add-timeseries": "Add timeseries", - "field-required": "Field is required", - "brokers": "Brokers", - "add-broker": "Add broker", - "host": "Host", - "port": "Port", - "port-range": "Port should be in a range from 1 to 65535.", + "unique-id-required": "Patreizējā paplašinājuma id jau eksistē.", + "delete": "Dzēst paplašinājumu", + "add": "Pievienot paplašinājumu", + "edit": "Rediģēt paplašinājumu", + "delete-extension-title": "Vai esat pārliecināts, ka vēlaties dzēst paplašinājumu '{{extensionId}}'?", + "delete-extension-text": "Esiet uzmanīgs, pēc apstiprinājuma paplašinājums un visi tā saistītie dati nebūs atjaunojami.", + "delete-extensions-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 extension} other {# paplašinājumus} }?", + "delete-extensions-text": "Esiet uzmanīgs, pēc apstiprinājuma visi atlasītie paplašinājumi tiks dzēsti.", + "converters": "Pārveidotāji", + "converter-id": "Pārveidotāja id", + "configuration": "Konfigurācija", + "converter-configurations": "Pārveidotāja konfigurācija", + "token": "Drošības tokens", + "add-converter": "Pievienot pārveidotāju", + "add-config": "Pievienot pārveidotāja konfigurāciju", + "device-name-expression": "Iekārtas nosaukuma izteiksme", + "device-type-expression": "Iekārtas tipa izteiksme", + "custom": "Pielāgot", + "to-double": "Dubutot", + "transformer": "Pārveidotājs", + "json-required": "json pārveidotājs ir nepieciešams.", + "json-parse": "Nav iespējams parsēt json pārveidotāju.", + "attributes": "Atribūti", + "add-attribute": "Pievienot atribūtus", + "add-map": "Pievienot kartēšanas elementu", + "timeseries": "Laika sērijas", + "add-timeseries": "Pievienot laika sērijas", + "field-required": "Lauks ir nepieciešams", + "brokers": "Brokeris", + "add-broker": "Pievienot brokeri", + "host": "Saimnieks", + "port": "Ports", + "port-range": "Portam jābūt robežās no 1 līdz 65535.", "ssl": "Ssl", - "credentials": "Credentials", - "username": "Username", - "password": "Password", - "retry-interval": "Retry interval in milliseconds", - "anonymous": "Anonymous", - "basic": "Basic", + "credentials": "Akreditācijas dati", + "username": "Lietotājvārds", + "password": "Parole", + "retry-interval": "Mēģināt vēlreiz intervāls milisekundēs", + "anonymous": "Anonīmi", + "basic": "Pamata", "pem": "PEM", - "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.", - "mapping": "Mapping", - "topic-filter": "Topic filter", - "converter-type": "Converter type", + "ca-cert": "CA sertifikācijas fails *", + "private-key": "Privātās atslēgas fails *", + "cert": "Srtifikāta fails *", + "no-file": "Nav fails izvēlēts.", + "drop-file": "Nosviest failu vai klikšķināt izvēlēto failu augšupielādei.", + "mapping": "Kartēšana", + "topic-filter": "Temata filtrs", + "converter-type": "Pārveidotāja tips", "converter-json": "Json", - "json-name-expression": "Device name json expression", - "topic-name-expression": "Device name topic expression", - "json-type-expression": "Device type json expression", - "topic-type-expression": "Device type topic expression", - "attribute-key-expression": "Attribute key expression", - "attr-json-key-expression": "Attribute key json expression", - "attr-topic-key-expression": "Attribute key topic expression", - "request-id-expression": "Request id expression", - "request-id-json-expression": "Request id json expression", - "request-id-topic-expression": "Request id topic expression", - "response-topic-expression": "Response topic expression", - "value-expression": "Value expression", - "topic": "Topic", - "timeout": "Timeout in milliseconds", - "converter-json-required": "Converter json is required.", - "converter-json-parse": "Unable to parse converter json.", - "filter-expression": "Filter expression", - "connect-requests": "Connect requests", - "add-connect-request": "Add connect request", - "disconnect-requests": "Disconnect requests", - "add-disconnect-request": "Add disconnect request", - "attribute-requests": "Attribute requests", - "add-attribute-request": "Add attribute request", - "attribute-updates": "Attribute updates", - "add-attribute-update": "Add attribute update", - "server-side-rpc": "Server side RPC", - "add-server-side-rpc-request": "Add server-side RPC request", - "device-name-filter": "Device name filter", - "attribute-filter": "Attribute filter", - "method-filter": "Method filter", - "request-topic-expression": "Request topic expression", - "response-timeout": "Response timeout in milliseconds", - "topic-expression": "Topic expression", - "client-scope": "Client scope", - "add-device": "Add device", - "opc-server": "Servers", - "opc-add-server": "Add server", - "opc-add-server-prompt": "Please add server", - "opc-application-name": "Application name", - "opc-application-uri": "Application uri", - "opc-scan-period-in-seconds": "Scan period in seconds", - "opc-security": "Security", - "opc-identity": "Identity", - "opc-keystore": "Keystore", - "opc-type": "Type", - "opc-keystore-type": "Type", - "opc-keystore-location": "Location *", - "opc-keystore-password": "Password", - "opc-keystore-alias": "Alias", - "opc-keystore-key-password": "Key password", - "opc-device-node-pattern": "Device node pattern", - "opc-device-name-pattern": "Device name pattern", - "modbus-server": "Servers/slaves", - "modbus-add-server": "Add server/slave", - "modbus-add-server-prompt": "Please add server/slave", - "modbus-transport": "Transport", - "modbus-tcp-reconnect": "Automatically reconnect", - "modbus-rtu-over-tcp": "RTU over TCP", - "modbus-port-name": "Serial port name", - "modbus-encoding": "Encoding", - "modbus-parity": "Parity", - "modbus-baudrate": "Baud rate", - "modbus-databits": "Data bits", + "json-name-expression": "Iekārtas nosaukuma json izteiksme", + "topic-name-expression": "Iekārtas nosaukuma temata izteiksme", + "json-type-expression": "Iekārtas tipa json izteiksme", + "topic-type-expression": "Iekārtas tipa temata izteiksme", + "attribute-key-expression": "Atribūtu atslēgas izteiksme", + "attr-json-key-expression": "Atribūtu atslēgas json izteiksme", + "attr-topic-key-expression": "Attribūtu atslēgas temata izteiksme", + "request-id-expression": "Pieprasīt id izteiksmi", + "request-id-json-expression": "Pieprasīt id json izteiksmi", + "request-id-topic-expression": "Pieprasīt id temata izteiksmi", + "response-topic-expression": "Atbildēt temata izteiksmi", + "value-expression": "Vērtības izteiksme", + "topic": "Temats", + "timeout": "Pārtraukums milisekundēs", + "converter-json-required": "json pārveidotājs ir nepieciešams.", + "converter-json-parse": "Nav iespējams parsēt pārveidotāju json.", + "filter-expression": "Filtra izteiksme", + "connect-requests": "Savienot pieprasījumus", + "add-connect-request": "Pievienot savienojuma pieprasījumus", + "disconnect-requests": "Atvienot pieprasījumus", + "add-disconnect-request": "Pievienot atvienot pieprasījumus", + "attribute-requests": "Attribūtu pieprasījumus", + "add-attribute-request": "Pievienot atribūtu pieprasījumu", + "attribute-updates": "Atribūtu atjaunojumi", + "add-attribute-update": "Pievienot atribūtu atjaunojumus", + "server-side-rpc": "Servera puses RPC", + "add-server-side-rpc-request": "Pievienot servera puses RPC pieprasījumus", + "device-name-filter": "Iekārtas nosaukuma filtrs", + "attribute-filter": "Atribūtu filtrs", + "method-filter": "Metodes filtrs", + "request-topic-expression": "Pieprasīt temata izteiksmi", + "response-timeout": "Atbildes pārtraukums milisekundēs", + "topic-expression": "Temata izteiksme", + "client-scope": "Klienta darbības joma", + "add-device": "Pievienot iekārtu", + "opc-server": "Serveris", + "opc-add-server": "Pievienot serveri", + "opc-add-server-prompt": "Lūdzu pievienot serveri", + "opc-application-name": "Aplikācijas nosaukums", + "opc-application-uri": "Aplikācijas uri", + "opc-scan-period-in-seconds": "Skanēt periodu sekundēs", + "opc-security": "Drošība", + "opc-identity": "Identitāte", + "opc-keystore": "Atslēgu veikals", + "opc-type": "Tips", + "opc-keystore-type": "Tips", + "opc-keystore-location": "Vieta *", + "opc-keystore-password": "Parole", + "opc-keystore-alias": "Segvārds", + "opc-keystore-key-password": "Atslēgas parole", + "opc-device-node-pattern": "Iekārtas nodes veids", + "opc-device-name-pattern": "Iekārtas nosaukuma veids", + "modbus-server": "Serveris/vergs", + "modbus-add-server": "Pievienot serveri/vergi", + "modbus-add-server-prompt": "Lūdzu pievienot serveri/vergu", + "modbus-transport": "Transports", + "modbus-tcp-reconnect": "Automātiski atkārtoti savienot", + "modbus-rtu-over-tcp": "RTU pa TCP", + "modbus-port-name": "Seriālā porta nosaukums", + "modbus-encoding": "Kodēšana", + "modbus-parity": "Paritāte", + "modbus-baudrate": "Pārraides ātrums", + "modbus-databits": "Datu bits", "modbus-stopbits": "Stop bits", - "modbus-databits-range": "Data bits should be in a range from 7 to 8.", - "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.", - "modbus-unit-id": "Unit ID", - "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.", - "modbus-device-name": "Device name", - "modbus-poll-period": "Poll period (ms)", - "modbus-attributes-poll-period": "Attributes poll period (ms)", - "modbus-timeseries-poll-period": "Timeseries poll period (ms)", - "modbus-poll-period-range": "Poll period should be positive value.", - "modbus-tag": "Tag", - "modbus-function": "Function", - "modbus-register-address": "Register address", - "modbus-register-address-range": "Register address should be in a range from 0 to 65535.", - "modbus-register-bit-index": "Bit index", - "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.", - "modbus-register-count": "Register count", - "modbus-register-count-range": "Register count should be a positive value.", - "modbus-byte-order": "Byte order", + "modbus-databits-range": "Datu bitiem jābūt no 7 līdz 8.", + "modbus-stopbits-range": "Stop bitiem jābūt no 1 līdz 2.", + "modbus-unit-id": "Iekārtas ID", + "modbus-unit-id-range": "Iekārtas ID jābūt no 1 līdz 247.", + "modbus-device-name": "Iekārtas nosaukums", + "modbus-poll-period": "Aptaujas periods (ms)", + "modbus-attributes-poll-period": "Atribūtu aptaujas periods (ms)", + "modbus-timeseries-poll-period": "Laika sērijas aptaujas periods (ms)", + "modbus-poll-period-range": "Aptaujas periodam jābūt pzitīvai vērtībai.", + "modbus-tag": "Etiķete", + "modbus-function": "Funkcija", + "modbus-register-address": "Reģistra adrese", + "modbus-register-address-range": "Reģistra adresei jābūt no 0 līdz 65535.", + "modbus-register-bit-index": "Bita indekss", + "modbus-register-bit-index-range": "Bita indeksam jābūt no 0 līdz 15.", + "modbus-register-count": "Reģistra skaitītājs", + "modbus-register-count-range": "Reģistra skaitītājam jābūt pzitīvai vērtībai.", + "modbus-byte-order": "Baitu kārtība", "sync": { - "status": "Status", + "status": "Statuss", "sync": "Sync", - "not-sync": "Not sync", - "last-sync-time": "Last sync time", - "not-available": "Not available" + "not-sync": "Nav sync", + "last-sync-time": "Pēdējais sync laiks", + "not-available": "Nav pieejams" }, - "export-extensions-configuration": "Export extensions configuration", - "import-extensions-configuration": "Import extensions configuration", - "import-extensions": "Import extensions", - "import-extension": "Import extension", - "export-extension": "Export extension", - "file": "Extensions file", - "invalid-file-error": "Invalid extension file" + "export-extensions-configuration": "Eksportēt paplašinājuma konfigurāciju", + "import-extensions-configuration": "Importēt paplašinājuma konfigurāciju", + "import-extensions": "Importēt paplašinājumus", + "import-extension": "Importēt paplašinājumu", + "export-extension": "Eksportēt paplašinājumu", + "file": "Paplašinājuma fails", + "invalid-file-error": "Invalīds paplašinājuma fails" }, "fullscreen": { - "expand": "Expand to fullscreen", - "exit": "Exit fullscreen", - "toggle": "Toggle fullscreen mode", - "fullscreen": "Fullscreen" + "expand": "Paplašināt uz pilnu ekrānu", + "exit": "Iziet no pilna ekrāna", + "toggle": "Pārslēgties uz pilna ekrāna režīmu", + "fullscreen": "Pilns ekrāns" }, "function": { - "function": "Function" + "function": "Funkcija" }, "grid": { - "delete-item-title": "Are you sure you want to delete this item?", - "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.", - "delete-items-title": "Are you sure you want to delete { count, plural, 1 {1 item} other {# items} }?", - "delete-items-action-title": "Delete { count, plural, 1 {1 item} other {# items} }", - "delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.", - "add-item-text": "Add new item", - "no-items-text": "No items found", - "item-details": "Item details", - "delete-item": "Delete Item", - "delete-items": "Delete Items", - "scroll-to-top": "Scroll to top" + "delete-item-title": "Vai esat pārliecināts, ka vēlaties dzēst šo priekšmetu?", + "delete-item-text": "Esiet uzmanīgs, pēc apstiprinājuma šī priekšmeta dati nebūs atjaunojami.", + "delete-items-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 item} other {# priekšmetus} }?", + "delete-items-action-title": "Dzēst { count, plural, 1 {1 item} other {# priekšmeti} }", + "delete-items-text": "Esiet uzmanīgs, pēc apstiprinājuma visi atlasītie priekšmeti tiks noņemti un to saistītie dati nebūs atjaunojami.", + "add-item-text": "Pievienot jaunu priekšmetu", + "no-items-text": "Nav priekšmeti atrasti", + "item-details": "Priekšmetu detaļas", + "delete-item": "Dzēst priekšmetu", + "delete-items": "Dzēst priekšmetus", + "scroll-to-top": "Iet uz sākumu" }, "help": { - "goto-help-page": "Go to help page" + "goto-help-page": "Ejiet uz palīdzības lapu" }, "home": { - "home": "Home", - "profile": "Profile", - "logout": "Logout", - "menu": "Menu", - "avatar": "Avatar", - "open-user-menu": "Open user menu" + "home": "Sākums", + "profile": "Profils", + "logout": "Izlogoties", + "menu": "Izvēlne", + "avatar": "Avatars", + "open-user-menu": "Atvērt lietotāja izvēlni" }, "import": { - "no-file": "No file selected", - "drop-file": "Drop a JSON file or click to select a file to upload.", - "drop-file-csv": "Drop a CSV file or click to select a file to upload.", - "column-value": "Value", - "column-title": "Title", - "column-example": "Example value data", - "column-key": "Attribute/telemetry key", - "csv-delimiter": "CSV delimiter", - "csv-first-line-header": "First line contains column names", - "csv-update-data": "Update attributes/telemetry", - "import-csv-number-columns-error": "A file should contain at least two columns", - "import-csv-invalid-format-error": "Invalid file format. Line: '{{line}}'", + "no-file": "Nav fails izvēlēts", + "drop-file": "Nosviest JSON failu vai klikšķināt uz atlasīto failu augšupielādei.", + "drop-file-csv": "Nosviest CSV failu vai klikšķināt uz atlasīto failu augšupielādei.", + "column-value": "Vērtība", + "column-title": "Virsraksts", + "column-example": "Piemēra vērtību dati", + "column-key": "Atribūts/telemetrijas atslēga key", + "csv-delimiter": "CSV kolonu atdalītājs", + "csv-first-line-header": "Pirmā līnija satur kolonu nosaukumus", + "csv-update-data": "Atjaunot atribūtu/telemetrija", + "import-csv-number-columns-error": "Failā jābūt vismaz divām kolonām", + "import-csv-invalid-format-error": "Invalīds faila formāts. Līnija: '{{line}}'", "column-type": { - "name": "Name", - "type": "Type", - "column-type": "Column type", - "client-attribute": "Client attribute", - "shared-attribute": "Shared attribute", - "server-attribute": "Server attribute", - "timeseries": "Timeseries", - "entity-field": "Entity field", - "access-token": "Access token" + "name": "Nosaukums", + "type": "Tips", + "column-type": "Kolonas tips", + "client-attribute": "Klienta atribūts", + "shared-attribute": "Dalītais atribūts", + "server-attribute": "Servera atribūts", + "timeseries": "Laika sērijas", + "entity-field": "Vienības lauks", + "access-token": "Piekļuves tokens" }, "stepper-text": { - "select-file": "Select a file", - "configuration": "Import configuration", - "column-type": "Select columns type", - "creat-entities": "Creating new entities", - "done": "Done" + "select-file": "Atlasīt failu", + "configuration": "Importēt konfigurāciju", + "column-type": "Atlasīt kolonas tipu", + "creat-entities": "Radīt jaunas vienības", + "done": "Darīts" }, "message": { - "create-entities": "{{count}} new entities were successfully created.", - "update-entities": "{{count}} entities were successfully updated.", - "error-entities": "There was an error creating {{count}} entities." + "create-entities": "{{count}} jaunas vienības sekmīgi radītas.", + "update-entities": "{{count}} vienības sekmīgi atjaunotas.", + "error-entities": "Te ir kļūda radot {{count}} vienības." } }, "item": { - "selected": "Selected" + "selected": "Atlasīts" }, "js-func": { - "no-return-error": "Function must return value!", - "return-type-mismatch": "Function must return value of '{{type}}' type!", - "tidy": "Tidy" + "no-return-error": "Funkcijai vajag atgriezt rezultātu!", + "return-type-mismatch": "Funkcijai vajag atgriezt rezultātu '{{type}}' !", + "tidy": "Sakopt" }, "key-val": { - "key": "Key", - "value": "Value", - "remove-entry": "Remove entry", - "add-entry": "Add entry", - "no-data": "No entries" + "key": "Atslēga", + "value": "Vērtība", + "remove-entry": "Noņemt ierakstu", + "add-entry": "Pievienot ierakstu", + "no-data": "Nav ierakstu" }, "layout": { - "layout": "Layout", - "manage": "Manage layouts", - "settings": "Layout settings", - "color": "Color", - "main": "Main", - "right": "Right", - "select": "Select target layout" + "layout": "Izkārtojums", + "manage": "Pārvaldīt izkārtojumu", + "settings": "Izkārtojuma iestatījumi", + "color": "Krāsa", + "main": "Galvenais", + "right": "Pa labi", + "select": "Atlasīt mērķa izkārtojumu" }, "legend": { - "direction": "Legend direction", - "position": "Legend position", - "show-max": "Show max value", - "show-min": "Show min value", - "show-avg": "Show average value", - "show-total": "Show total value", - "settings": "Legend settings", + "direction": "Leģendas virziens", + "position": "Leģendas pozīcija", + "show-max": "Rādīt max vērtību", + "show-min": "Rādīt min vērtību", + "show-avg": "Rādīt vidējo vērtību", + "show-total": "Rādīt kopējo vērtību", + "settings": "Leģendas iestatījumi", "min": "min", "max": "max", - "avg": "avg", + "avg": "vidējais", "total": "total" }, "login": { "login": "Login", - "request-password-reset": "Request Password Reset", - "reset-password": "Reset Password", - "create-password": "Create Password", - "passwords-mismatch-error": "Entered passwords must be same!", - "password-again": "Password again", - "sign-in": "Please sign in", - "username": "Username (email)", - "remember-me": "Remember me", - "forgot-password": "Forgot Password?", - "password-reset": "Password reset", - "new-password": "New password", - "new-password-again": "New password again", - "password-link-sent-message": "Password reset link was successfully sent!", + "request-password-reset": "Pieprasīt atiestatīt paroli", + "reset-password": "Atiestatīt paroli", + "create-password": "Radīt paroli", + "passwords-mismatch-error": "Ievadītajai parolei ir jāsakrīt!", + "password-again": "Atkārtot paroli", + "sign-in": "Lūdzu pierakstīties", + "username": "Lietotājvārds (email)", + "remember-me": "Atcerēties mani", + "forgot-password": "Aizmirsu paroli?", + "password-reset": "Paroli atiestatīt", + "new-password": "Jaunā parole", + "new-password-again": "Atkārtot jauno paroli", + "password-link-sent-message": "paroles atiestatīšanas saite sekmīgi nosūtīta!", "email": "Email" }, "position": { - "top": "Top", - "bottom": "Bottom", - "left": "Left", - "right": "Right" + "top": "Sākums", + "bottom": "Beigas", + "left": "Pa kreisi", + "right": "Pa labi" }, "profile": { - "profile": "Profile", - "change-password": "Change Password", - "current-password": "Current password" + "profile": "Profils", + "change-password": "Mainīt paroli", + "current-password": "Patreizējā parole" }, "relation": { - "relations": "Relations", - "direction": "Direction", + "relations": "Attiecības", + "direction": "Virziens", "search-direction": { - "FROM": "From", - "TO": "To" + "FROM": "No", + "TO": "Uz" }, "direction-type": { - "FROM": "from", - "TO": "to" + "FROM": "No", + "TO": "Uz" }, - "from-relations": "Outbound relations", - "to-relations": "Inbound relations", - "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected", - "type": "Type", - "to-entity-type": "To entity type", - "to-entity-name": "To entity name", - "from-entity-type": "From entity type", - "from-entity-name": "From entity name", - "to-entity": "To entity", - "from-entity": "From entity", - "delete": "Delete relation", - "relation-type": "Relation type", - "relation-type-required": "Relation type is required.", - "any-relation-type": "Any type", - "add": "Add relation", - "edit": "Edit relation", - "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?", - "delete-to-relation-text": "Be careful, after the confirmation the entity '{{entityName}}' will be unrelated from the current entity.", - "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", - "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.", - "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?", - "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.", - "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?", - "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.", - "remove-relation-filter": "Remove relation filter", - "add-relation-filter": "Add relation filter", - "any-relation": "Any relation", - "relation-filters": "Relation filters", - "additional-info": "Additional info (JSON)", - "invalid-additional-info": "Unable to parse additional info json." + "from-relations": "Izejošāsa attiecības", + "to-relations": "Ienākošās attiecības", + "selected-relations": "{ count, plural, 1 {1 relation} other {# attiecības} } atlasītas", + "type": "Tips", + "to-entity-type": "Uz vienību tipu", + "to-entity-name": "Uz vienību nosaukumu", + "from-entity-type": "No vienību tipa", + "from-entity-name": "No vienību nosaukuma", + "to-entity": "Uz vienību", + "from-entity": "No vienības", + "delete": "Dzēst attiecību", + "relation-type": "Attiecības tips", + "relation-type-required": "Attiecību tips ir nepieciešams.", + "any-relation-type": "Jebkura tipa", + "add": "Pievienot attiecību", + "edit": "Rediģēt attiecību", + "delete-to-relation-title": "Vai esat pārliecināts, ka vēlaties dzēst attiecību uz vienību '{{entityName}}'?", + "delete-to-relation-text": "Esiet uzmanīgs, pēc apstiprinājuma vienība '{{entityName}}' būs atsaistīta no patreizējās vienības.", + "delete-to-relations-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 relation} other {# attiecības} }?", + "delete-to-relations-text": "Esiet uzmanīgs, pēc apstiprināšanas visas atlasītās attiecības būs noņemtas un attiecīgās vienības būs atsaistītas no patreizējās vienības.", + "delete-from-relation-title": "Vai esat pārliecināts, ka vēlaties dzēst attiecību no vienības '{{entityName}}'?", + "delete-from-relation-text": "Esiet uzmanīgs, pēc apstiprinājuma patreizējā vienība būs atsaistīta no vienības '{{entityName}}'.", + "delete-from-relations-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 relation} other {# attiecības} }?", + "delete-from-relations-text": "Esiet uzmanīgs, pēc apstiprinājuma visas atlasītās attiecības būs noņemtas un patreizējā vienība tiks atsaistīta no attiecīgās vienības.", + "remove-relation-filter": "Noņemt attiecību filtru", + "add-relation-filter": "Pievienot attiecību filtru", + "any-relation": "Jebkura attiecība", + "relation-filters": "Attiecību filtrs", + "additional-info": "Papildus info (JSON)", + "invalid-additional-info": "Nav iespēja parsēt papildus info json." }, "rulechain": { - "rulechain": "Rule chain", - "rulechains": "Rule chains", - "root": "Root", - "delete": "Delete rule chain", - "name": "Name", - "name-required": "Name is required.", - "description": "Description", - "add": "Add Rule Chain", - "set-root": "Make rule chain root", - "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", - "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", - "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?", - "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.", - "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?", - "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }", - "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.", - "add-rulechain-text": "Add new rule chain", - "no-rulechains-text": "No rule chains found", - "rulechain-details": "Rule chain details", - "details": "Details", - "events": "Events", - "system": "System", - "import": "Import rule chain", - "export": "Export rule chain", - "export-failed-error": "Unable to export rule chain: {{error}}", - "create-new-rulechain": "Create new rule chain", - "rulechain-file": "Rule chain file", - "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.", - "copyId": "Copy rule chain Id", - "idCopiedMessage": "Rule chain Id has been copied to clipboard", - "select-rulechain": "Select rule chain", - "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", - "rulechain-required": "Rule chain is required", - "management": "Rules management", - "debug-mode": "Debug mode" + "rulechain": "Noteikumu ķēde", + "rulechains": "Noteikumu ķēdes", + "root": "Sakne", + "delete": "Dzēsts noteikumu ķēdi", + "name": "Nosaukums", + "name-required": "Nosaukums ir nepieciešams.", + "description": "Nosaukums ir nepieciešams", + "add": "Pievienot noteikumu ķēdi", + "set-root": "Veidot noteikumu ķēdi kā sakni", + "set-root-rulechain-title": "Vai esat pārliecināts, ka vēlaties veidot noteikumu ķēdi '{{ruleChainName}}' kā sakni?", + "set-root-rulechain-text": "Pēc apstiprinājuma noteikuma ķēde tiks veidota kā sakne un apstrādās visu ienākošo informāciju.", + "delete-rulechain-title": "Vai esat pārliecināts, ka vēlaties dzēst noteikumu ķēdi '{{ruleChainName}}'?", + "delete-rulechain-text": "Esiet uzmanīgs, pēc apstiprinājuma noteikumu ķēde un saistītā informācija nebūs atjaunojama.", + "delete-rulechains-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 rule chain} other {# noteikumu ķēdes} }?", + "delete-rulechains-action-title": "Dzēst { count, plural, 1 {1 rule chain} other {# noteikumu ķēdes} }", + "delete-rulechains-text": "Esiet uzmanīgs, pēc apstiprinājuma visas atlasītās noteikumu ķēdes tiks noņemtas un to saistītos datus nevarēs atjaunot.", + "add-rulechain-text": "Pievienot jaunu noteikumu ķēdi", + "no-rulechains-text": "Nav noteikumu ķēdes atrastas", + "rulechain-details": "Noteikumu ķēdes detaļas", + "details": "Detaļas", + "events": "Notikumi", + "system": "Sistēma", + "import": "Importēt noteikumu ķēdi", + "export": "Eksportēt noteikumu ķēdi", + "export-failed-error": "Nav iespējams eksportēt noteikumu ķēdi: {{error}}", + "create-new-rulechain": "Radīt jaunu noteikumu ķēdi", + "rulechain-file": "Noteikumu ķēdes fails", + "invalid-rulechain-file-error": "Nav iespējams importēt noteikumu ķēdi: Invalīda noteikumu ķēdes datu struktūra.", + "copyId": "Kopēt noteikumu ķēdes Id", + "idCopiedMessage": "Noteikumu ķēdes Id ir kopēta uz starpliktuves", + "select-rulechain": "Atlasīt noteikumu ķēdi", + "no-rulechains-matching": "Nav noteikumu ķēdes atbilstības '{{entity}}' atrastas.", + "rulechain-required": "Noteikumu ķēde ir nepieciešama", + "management": "Noteikumu pārvaldība", + "debug-mode": "Atkļūdošanas režīms" }, "rulenode": { - "details": "Details", - "events": "Events", - "search": "Search nodes", - "open-node-library": "Open node library", - "add": "Add rule node", - "name": "Name", - "name-required": "Name is required.", - "type": "Type", - "description": "Description", - "delete": "Delete rule node", - "select-all-objects": "Select all nodes and connections", - "deselect-all-objects": "Deselect all nodes and connections", - "delete-selected-objects": "Delete selected nodes and connections", - "delete-selected": "Delete selected", - "select-all": "Select all", - "copy-selected": "Copy selected", - "deselect-all": "Deselect all", - "rulenode-details": "Rule node details", - "debug-mode": "Debug mode", - "configuration": "Configuration", - "link": "Link", - "link-details": "Rule node link details", - "add-link": "Add link", - "link-label": "Link label", - "link-label-required": "Link label is required.", - "custom-link-label": "Custom link label", - "custom-link-label-required": "Custom link label is required.", - "link-labels": "Link labels", - "link-labels-required": "Link labels is required.", - "no-link-labels-found": "No link labels found", - "no-link-label-matching": "'{{label}}' not found.", - "create-new-link-label": "Create a new one!", - "type-filter": "Filter", - "type-filter-details": "Filter incoming messages with configured conditions", - "type-enrichment": "Enrichment", - "type-enrichment-details": "Add additional information into Message Metadata", - "type-transformation": "Transformation", - "type-transformation-details": "Change Message payload and Metadata", - "type-action": "Action", - "type-action-details": "Perform special action", - "type-external": "External", - "type-external-details": "Interacts with external system", - "type-rule-chain": "Rule Chain", - "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", - "type-input": "Input", - "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node", - "type-unknown": "Unknown", - "type-unknown-details": "Unresolved Rule Node", - "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", - "ui-resources-load-error": "Failed to load configuration ui resources.", - "invalid-target-rulechain": "Unable to resolve target rule chain!", - "test-script-function": "Test script function", - "message": "Message", - "message-type": "Message type", - "select-message-type": "Select message type", - "message-type-required": "Message type is required", - "metadata": "Metadata", - "metadata-required": "Metadata entries can't be empty.", - "output": "Output", - "test": "Test", - "help": "Help", - "reset-debug-mode": "Reset debug mode in all nodes" + "details": "Detaļas", + "events": "Notikumi", + "search": "Meklēt nodes", + "open-node-library": "Atvērt node bibliotēku", + "add": "Pievienot noteikumu nodi", + "name": "Nosaukums", + "name-required": "Nosaukums ir nepieciešams.", + "type": "Tips", + "description": "Apraksts", + "delete": "Dzēst noteikumu nodi", + "select-all-objects": "Atlasīt visas nodes un savienojumus", + "deselect-all-objects": "Noņemt visas nodes un savienojumus", + "delete-selected-objects": "Dzēst atlasītās nodes un savienojumus", + "delete-selected": "Dzēst atlasītos", + "select-all": "Atlasīt visu", + "copy-selected": "Kopēt atlasīto", + "deselect-all": "Noņemt visu", + "rulenode-details": "Noteikumu nodes detaļas", + "debug-mode": "Atkļūdošanas mode", + "configuration": "Konfigurācija", + "link": "Saite", + "link-details": "Noteikumu nodes saites detaļas", + "add-link": "Pievienot saiti", + "link-label": "Saites etiķete", + "link-label-required": "Saites etiķete ir nepieciešama.", + "custom-link-label": "Klienta saites etiķete", + "custom-link-label-required": "Klienta saites etiķete ir nepieciešama.", + "link-labels": "Saites etiķetes", + "link-labels-required": "Saites etiķetes ir nepieciešamas.", + "no-link-labels-found":"Nav saites etiķetes atrastas", + "no-link-label-matching": "'{{label}}' nav atrasta.", + "create-new-link-label": "Radīt jaunu!", + "type-filter": "Filtrs", + "type-filter-details": "Filtrēt ienākošos ziņojumus ar konfigurētajiem stāvokļiem", + "type-enrichment": "Bagātināšana", + "type-enrichment-details": "Pievieno papildus informāciju ziņas metadatiem", + "type-transformation": "Transformācija", + "type-transformation-details": "Mainīt ziņas datu lauku un metatdatus", + "type-action": "Aktivitāte", + "type-action-details": "Veikt specifisku aktivitāti", + "type-external": "Ārējs", + "type-external-details": "Sadarboties ar ārējām sistēmām", + "type-rule-chain": "Noteikumu ķēde", + "type-rule-chain-details": "Pārsūta ienākošo ziņu uz specifisku noteikumu ķēdi ", + "type-input": "Ievads", + "type-input-details": "Loģiskais ievads noteikumu ķēdei, pārsūta ienākošās ziņas uz nākamo attiecināto noteikumu nodi", + "type-unknown": "Nezināms", + "type-unknown-details": "Neatrisināta noteikumu node", + "directive-is-not-loaded": "Noteikta konfigurācijas direktīva '{{directiveName}}' nav pieejama.", + "ui-resources-load-error": "Nesekmīgs mēģinājums ielādēt konfigurācijas ui resursus.", + "invalid-target-rulechain": "Nav iespējams atrisināt mērķa noteikumu ķēdi!", + "test-script-function": "Testēt skripta funkciju", + "message": "Ziņa", + "message-type": "Ziņas tips", + "select-message-type": "Atlasīt ziņas tipu", + "message-type-required": "Ziņas tips ir nepieciešams", + "metadata": "Metadati", + "metadata-required": "Metadatu ievadi nevar būt tukši.", + "output": "Izeja", + "test": "Tests", + "help": "Palīdzība", + "reset-debug-mode": "Atiestatīt atkļūdošanu visās nodēs" }, "tenant": { - "tenant": "Tenant", - "tenants": "Tenants", - "management": "Tenant management", - "add": "Add Tenant", - "admins": "Admins", - "manage-tenant-admins": "Manage tenant admins", - "delete": "Delete tenant", - "add-tenant-text": "Add new tenant", - "no-tenants-text": "No tenants found", - "tenant-details": "Tenant details", - "delete-tenant-title": "Are you sure you want to delete the tenant '{{tenantTitle}}'?", - "delete-tenant-text": "Be careful, after the confirmation the tenant and all related data will become unrecoverable.", - "delete-tenants-title": "Are you sure you want to delete { count, plural, 1 {1 tenant} other {# tenants} }?", - "delete-tenants-action-title": "Delete { count, plural, 1 {1 tenant} other {# tenants} }", - "delete-tenants-text": "Be careful, after the confirmation all selected tenants will be removed and all related data will become unrecoverable.", - "title": "Title", - "title-required": "Title is required.", - "description": "Description", - "details": "Details", - "events": "Events", - "copyId": "Copy tenant Id", - "idCopiedMessage": "Tenant Id has been copied to clipboard", - "select-tenant": "Select tenant", - "no-tenants-matching": "No tenants matching '{{entity}}' were found.", - "tenant-required": "Tenant is required" + "tenant": "Īrnieks", + "tenants": "Īrnieki", + "management": "Īrnieku pārvaldība", + "add": "Pievienot īrnieku", + "admins": "Administrātori", + "manage-tenant-admins": "Pārvaldīt īrnieku administrātorus", + "delete": "Dzēst īrnieku", + "add-tenant-text": "Pievienot jaunu īrnieku", + "no-tenants-text": "Nav īrnieki atrasti", + "tenant-details": "Īrnieka detaļas", + "delete-tenant-title": "Vai esat pārliecināts, ka vēlaties dzēst īrnieku '{{tenantTitle}}'?", + "delete-tenant-text": "Esiet uzmanīgs, pēc apstiprinājuma īrnieks un tā saistītie dati nebūs atjaunojami.", + "delete-tenants-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 tenant} other {# īrniekus} }?", + "delete-tenants-action-title": "Dzēst { count, plural, 1 {1 tenant} other {# īrniekus} }", + "delete-tenants-text": "Esiet uzmanīgs, pēc apstiprinājuma visi atlasītie īrnieki tiks noņemti un saistītie dati nebūs atjaunojami.", + "title": "Virsraksts", + "title-required": "Virsraksts ir nepieciešams.", + "description": "Apraksts", + "details": "Detaļas", + "events": "Notikumi", + "copyId": "Kopēt īrnieka Id", + "idCopiedMessage": "Īrnieka Id ir kopēta uz starpliktuvi", + "select-tenant": "Atlasīt īrnieku", + "no-tenants-matching": "Nav īrnieku saderības '{{entity}}' atrastas.", + "tenant-required": "Īrnieks ir nepieciešams" }, "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": "Days", - "hours": "Hours", - "minutes": "Minutes", - "seconds": "Seconds", - "advanced": "Advanced" + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# sekundes} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minūtes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# stundas} }", + "days-interval": "{ days, plural, 1 {1 day} other {# dienas} }", + "days": "Dienas", + "hours": "Stundas", + "minutes": "Minūtes", + "seconds": "Sekundes", + "advanced": "Pieredzējis lietotājs" }, "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": "Realtime", - "history": "History", - "last-prefix": "last", - "period": "from {{ startTime }} to {{ endTime }}", - "edit": "Edit timewindow", - "date-range": "Date range", - "last": "Last", - "time-period": "Time period" + "days": "{ days, plural, 1 { day } other {# dienas } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# stundas } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minūtes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# sekundes } }", + "realtime": "Reālajā laikā", + "history": "Vēsture", + "last-prefix": "Pēdējās", + "period": "No {{ startTime }} to {{ endTime }}", + "edit": "Rediģēt laika logu", + "date-range": "Datumu diapazons", + "last": "Pēdējās", + "time-period": "Laika periods" }, "user": { - "user": "User", - "users": "Users", - "customer-users": "Customer Users", - "tenant-admins": "Tenant Admins", - "sys-admin": "System administrator", - "tenant-admin": "Tenant administrator", - "customer": "Customer", - "anonymous": "Anonymous", - "add": "Add User", - "delete": "Delete user", - "add-user-text": "Add new user", - "no-users-text": "No users found", - "user-details": "User details", - "delete-user-title": "Are you sure you want to delete the user '{{userEmail}}'?", - "delete-user-text": "Be careful, after the confirmation the user and all related data will become unrecoverable.", - "delete-users-title": "Are you sure you want to delete { count, plural, 1 {1 user} other {# users} }?", - "delete-users-action-title": "Delete { count, plural, 1 {1 user} other {# users} }", - "delete-users-text": "Be careful, after the confirmation all selected users will be removed and all related data will become unrecoverable.", - "activation-email-sent-message": "Activation email was successfully sent!", - "resend-activation": "Resend activation", + "user": "Lietotājs", + "users": "Lietotāji", + "customer-users": "Klienta lietotāji", + "tenant-admins": "Īrnieka administrātori", + "sys-admin": "Sistēmas administrātori", + "tenant-admin": "Īrnieka administrātors", + "customer": "Klients", + "anonymous": "Anonīmi", + "add": "Pievienot lietotāju", + "delete": "Dzēst lietotāju", + "add-user-text": "Pievienot jaunu lietotāju", + "no-users-text": "Nav lietotāji atrasti", + "user-details": "Lietotāja detaļas", + "delete-user-title": "Vai esat pārliecināts, ka vēlaties dzēst lietotāju '{{userEmail}}'?", + "delete-user-text": "Esiet uzmanīgs, pēc apstiprinājuma lietotājs un tā saistītie dati nebūs atjaunojami.", + "delete-users-title": "Vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 user} other {# lietotājus} }?", + "delete-users-action-title": "Dzēst { count, plural, 1 {1 user} other {# lietotājus} }", + "delete-users-text": "Esiet uzmanīgs, pēc apstiprinājuma visi atlasītie lietotāji tiks noņemti un to saistītie dati nebūs atjaunojami.", + "activation-email-sent-message": "Aktivizācijas email ir sekmīgi nosūtīts!", + "resend-activation": "Atkārtoti nosūtīt aktivizāciju", "email": "Email", - "email-required": "Email is required.", - "invalid-email-format": "Invalid email format.", - "first-name": "First Name", - "last-name": "Last Name", - "description": "Description", - "default-dashboard": "Default dashboard", - "always-fullscreen": "Always fullscreen", - "select-user": "Select user", - "no-users-matching": "No users matching '{{entity}}' were found.", - "user-required": "User is required", - "activation-method": "Activation method", - "display-activation-link": "Display activation link", - "send-activation-mail": "Send activation mail", - "activation-link": "User activation link", - "activation-link-text": "In order to activate user use the following activation link :", - "copy-activation-link": "Copy activation link", - "activation-link-copied-message": "User activation link has been copied to clipboard", - "details": "Details", - "login-as-tenant-admin": "Login as Tenant Admin", - "login-as-customer-user": "Login as Customer User" + "email-required": "Email ir nepieciešams.", + "invalid-email-format": "Invalīds email formāts.", + "first-name": "Vārds", + "last-name": "Uzvārds", + "description": "Apraksts", + "default-dashboard": "Defaultais panelis", + "always-fullscreen": "Vienmēr pilnekrāna", + "select-user": "Izvēlēties lietotāju", + "no-users-matching": "Nav lietotāju atbilstības '{{entity}}' atrastas.", + "user-required": "Lietotājs ir nepieciešams", + "activation-method": "Aktivizācijas veids", + "display-activation-link": "Parādīt aktivizācijas saiti", + "send-activation-mail": "Nosūtīt aktivizācijas email", + "activation-link": "Lietotāja aktivizācijas saite", + "activation-link-text": "Lai aktivizētu lietotāju, lieto sekojošo aktivizācijas saiti :", + "copy-activation-link": "Kopēt aktivizācijas saiti", + "activation-link-copied-message": "Lietotāja aktivizācijas saite ir kopēta uz starpliktuvi", + "details": "Detaļas", + "login-as-tenant-admin": "Login kā īrnieka administrātors", + "login-as-customer-user": "Login kā klienta lietotājs" }, "value": { - "type": "Value type", - "string": "String", - "string-value": "String value", - "integer": "Integer", - "integer-value": "Integer value", - "invalid-integer-value": "Invalid integer value", - "double": "Double", - "double-value": "Double value", - "boolean": "Boolean", - "boolean-value": "Boolean value", - "false": "False", - "true": "True", - "long": "Long" + "type": "Vērtības tips", + "string": "Teksts", + "string-value": "Teksta informācija", + "integer": "Skaitlis", + "integer-value": "Skaitļa vērtība", + "invalid-integer-value": "Invalīda skaitļa vērtība", + "double": "Skaitlis ar cipariem aiz komata", + "double-value": "Skaitļa ar cipariem aiz komata vērtība", + "boolean": "ir/nav", + "boolean-value": "ir/nav vērtības", + "false": "Nepareizi", + "true": "Patiesi", + "long": "Ilgāk" }, "widget": { - "widget-library": "Widgets Library", - "widget-bundle": "Widgets Bundle", - "select-widgets-bundle": "Select widgets bundle", - "management": "Widget management", - "editor": "Widget Editor", - "widget-type-not-found": "Problem loading widget configuration.
Probably associated\n widget type was removed.", - "widget-type-load-error": "Widget wasn't loaded due to the following errors:", - "remove": "Remove widget", - "edit": "Edit widget", - "remove-widget-title": "Are you sure you want to remove the widget '{{widgetTitle}}'?", - "remove-widget-text": "After the confirmation the widget and all related data will become unrecoverable.", - "timeseries": "Time series", - "search-data": "Search data", - "no-data-found": "No data found", - "latest-values": "Latest values", - "rpc": "Control widget", - "alarm": "Alarm widget", - "static": "Static widget", - "select-widget-type": "Select widget type", - "missing-widget-title-error": "Widget title must be specified!", - "widget-saved": "Widget saved", - "unable-to-save-widget-error": "Unable to save widget! Widget has errors!", - "save": "Save widget", - "saveAs": "Save widget as", - "save-widget-type-as": "Save widget type as", - "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle", - "toggle-fullscreen": "Toggle fullscreen", - "run": "Run widget", - "title": "Widget title", - "title-required": "Widget title is required.", - "type": "Widget type", - "resources": "Resources", + "widget-library": "Logrīku bibliotēka", + "widget-bundle": "Logrīku apkopojums", + "select-widgets-bundle": "Atlasīt logrīku apkopojumu", + "management": "Logrīku pārvaldība", + "editor": "Logrīku rediģētājs", + "widget-type-not-found": "Problēma ielādēt logrīka konfigurāciju.
Iespējams, ka attiecīgais logrīka tips ir noņemts.", + "widget-type-load-error": "Logrīks nav ielādēts dēl sekojošajām kļūdām:", + "remove": "Noņemt logrīku", + "edit": "rediģēt logrīku", + "remove-widget-title": "Vai esat pārliecināts, ka vēlaties noņemt logrīku '{{widgetTitle}}'?", + "remove-widget-text": "Pēc apstiprinājuma logrīks un tā saistītā informācija nebūs atjaunojama.", + "timeseries": "Laika sērijas", + "search-data": "Meklēt datus", + "no-data-found": "Nav datu atrasti", + "latest-values": "Pedējās vērtības", + "rpc": "Kontroles logrīks", + "alarm": "Trauksmes logrīks", + "static": "Statiskais logrīks", + "select-widget-type": "Atlasīt logrīka tipu", + "missing-widget-title-error": "Logrīka virsrakstam vajag būt norādītam!", + "widget-saved": "Logrīks saglabāts", + "unable-to-save-widget-error": "Nav iespēja saglabāt logrīku! Logrīkam ir kļūdas!", + "save": "Saglabāt logrīku", + "saveAs": "Saglabāt logrīku kā", + "save-widget-type-as": "Saglabāt logrīka tipu kā", + "save-widget-type-as-text": "Lūdzu ievadīt jaunu logrīka virsrakstu un/vai atlasīt mērķa logrīku apkopojumu", + "toggle-fullscreen": "Parslēgt pilnekrānu", + "run": "Palaist logrīku", + "title": "Logrīka virsraksts", + "title-required": "Logrīka virsraksts ir nepieciešams.", + "type": "Logrīka tips", + "resources": "Resursi", "resource-url": "JavaScript/CSS URL", - "remove-resource": "Remove resource", - "add-resource": "Add resource", + "remove-resource": "Noņemt resursus", + "add-resource": "Pievienot resursus", "html": "HTML", "tidy": "Tidy", "css": "CSS", - "settings-schema": "Settings schema", - "datakey-settings-schema": "Data key settings schema", + "settings-schema": "Iestatījumu shēma", + "datakey-settings-schema": "Datu atslēgas iestatījumu shēma", "javascript": "Javascript", - "remove-widget-type-title": "Are you sure you want to remove the widget type '{{widgetName}}'?", - "remove-widget-type-text": "After the confirmation the widget type and all related data will become unrecoverable.", - "remove-widget-type": "Remove widget type", - "add-widget-type": "Add new widget type", - "widget-type-load-failed-error": "Failed to load widget type!", - "widget-template-load-failed-error": "Failed to load widget template!", - "add": "Add Widget", - "undo": "Undo widget changes", - "export": "Export widget" + "remove-widget-type-title": "Vai esat pārliecināts, ka vēlaties noņemt logrīka tipu '{{widgetName}}'?", + "remove-widget-type-text": "Pēc apstiprinājuma logrīka tips un tā saistītie dati nebūs atjaunojami.", + "remove-widget-type": "Noņemt logrīka tipu", + "add-widget-type": "Pievienot jaunu logrīka tipu", + "widget-type-load-failed-error": "Neveiksme ielādēt logrīka tipu!", + "widget-template-load-failed-error": "Neveiksme ielādēt logrīka paraugu!", + "add": "Pievienot logrīku", + "undo": "Atcelt logrīka izmaiņas", + "export": "Eksportēt logrīku" }, "widget-action": { - "header-button": "Widget header button", - "open-dashboard-state": "Navigate to new dashboard state", - "update-dashboard-state": "Update current dashboard state", - "open-dashboard": "Navigate to other dashboard", - "custom": "Custom action", - "target-dashboard-state": "Target dashboard state", - "target-dashboard-state-required": "Target dashboard state is required", - "set-entity-from-widget": "Set entity from widget", - "target-dashboard": "Target dashboard", - "open-right-layout": "Open right dashboard layout (mobile view)" + "header-button": "Logrīka galvenes poga", + "open-dashboard-state": "Navigēt uz jaunu paneļa stāvokli", + "update-dashboard-state": "Atjaunot patreizējo paneļa stāvokli", + "open-dashboard": "Navigēt uz citu paneli", + "custom": "Klienta aktivitāte", + "target-dashboard-state": "Mērķa paneļa stāvoklis", + "target-dashboard-state-required": "Mērķa paneļa stāvoklis ir nepieciešams", + "set-entity-from-widget": "Uzstādīt vienību no logrīka", + "target-dashboard": "Mērķa panelis", + "open-right-layout": "Atvērt pareizo paneļa izkārtojumu (mobilais skats)" }, "widgets-bundle": { - "current": "Current bundle", - "widgets-bundles": "Widgets Bundles", - "add": "Add Widgets Bundle", - "delete": "Delete widgets bundle", - "title": "Title", - "title-required": "Title is required.", - "add-widgets-bundle-text": "Add new widgets bundle", - "no-widgets-bundles-text": "No widgets bundles found", - "empty": "Widgets bundle is empty", - "details": "Details", - "widgets-bundle-details": "Widgets bundle details", - "delete-widgets-bundle-title": "Are you sure you want to delete the widgets bundle '{{widgetsBundleTitle}}'?", - "delete-widgets-bundle-text": "Be careful, after the confirmation the widgets bundle and all related data will become unrecoverable.", - "delete-widgets-bundles-title": "Are you sure you want to delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", - "delete-widgets-bundles-action-title": "Delete { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", - "delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.", - "no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.", - "widgets-bundle-required": "Widgets bundle is required.", - "system": "System", - "import": "Import widgets bundle", - "export": "Export widgets bundle", - "export-failed-error": "Unable to export widgets bundle: {{error}}", - "create-new-widgets-bundle": "Create new widgets bundle", - "widgets-bundle-file": "Widgets bundle file", - "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure." + "current": "Patreizējais apkopojums", + "widgets-bundles": "Logrīku apkopojuma", + "add": "Pievienot logrīku apkopojumu", + "delete": "Dzēst logrīku apkopojumu", + "title": "Virsraksts", + "title-required": "Virsraksts ir nepieciešams.", + "add-widgets-bundle-text": "Pievienot jaunu logrīku apkopojumu", + "no-widgets-bundles-text": "Nav logrīku apkopojumi atrasti", + "empty": "Logrīku apkopojums ir tukšs", + "details": "Detaļas", + "widgets-bundle-details": "Logrīku apkopojumu detaļas", + "delete-widgets-bundle-title": "Vai esat pārliecināts, ka vēlaties dzēst logrīku apkopojumu '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Esiet uzmanīgs, pēc apstiprinājuma logrīka apkopojums un tā saistītie dati nebūs atjaunojami.", + "delete-widgets-bundles-title": "vai esat pārliecināts, ka vēlaties dzēst { count, plural, 1 {1 widgets bundle} other {# logrīku apkopojumus} }?", + "delete-widgets-bundles-action-title": "Dzēst { count, plural, 1 {1 widgets bundle} other {# logrīku apkopojumus} }", + "delete-widgets-bundles-text": "Esiet uzmanīgs, pēc apstiprinājuma visi atlasītie logrīku apkopojumi tiks noņemti un to saistītie dati nebūs atjaunojami.", + "no-widgets-bundles-matching": "Nav logrīku apkopojumu saderības '{{widgetsBundle}}' atrastas.", + "widgets-bundle-required": "Logrīku apkopojums ir nepieciešams.", + "system": "Sistēma", + "import": "Importēt logrīku apkopojumu", + "export": "Eksportēt logrīku apkopojumu", + "export-failed-error": "Nav iespējams eksportēt logrīku apkopojumu: {{error}}", + "create-new-widgets-bundle": "Radīt jaunu logrīku apkopojumu", + "widgets-bundle-file": "Logrīku apkopojumu fails", + "invalid-widgets-bundle-file-error": "Nav iespējams importēt logrīku apkopojumu: Invalīda logrīku apkopojuma datu struktūra." }, "widget-config": { - "data": "Data", - "settings": "Settings", - "advanced": "Advanced", - "title": "Title", - "general-settings": "General settings", - "display-title": "Display title", - "drop-shadow": "Drop shadow", - "enable-fullscreen": "Enable fullscreen", - "background-color": "Background color", - "text-color": "Text color", - "padding": "Padding", - "margin": "Margin", - "widget-style": "Widget style", - "title-style": "Title style", - "mobile-mode-settings": "Mobile mode settings", - "order": "Order", - "height": "Height", - "units": "Special symbol to show next to value", - "decimals": "Number of digits after floating point", - "timewindow": "Timewindow", - "use-dashboard-timewindow": "Use dashboard timewindow", - "display-timewindow": "Display timewindow", - "display-legend": "Display legend", - "datasources": "Datasources", - "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", - "datasource-type": "Type", - "datasource-parameters": "Parameters", - "remove-datasource": "Remove datasource", - "add-datasource": "Add datasource", - "target-device": "Target device", - "alarm-source": "Alarm source", - "actions": "Actions", - "action": "Action", - "add-action": "Add action", - "search-actions": "Search actions", - "action-source": "Action source", - "action-source-required": "Action source is required.", - "action-name": "Name", - "action-name-required": "Action name is required.", - "action-name-not-unique": "Another action with the same name already exists.
Action name should be unique within the same action source.", - "action-icon": "Icon", - "action-type": "Type", - "action-type-required": "Action type is required.", - "edit-action": "Edit action", - "delete-action": "Delete action", - "delete-action-title": "Delete widget action", - "delete-action-text": "Are you sure you want delete widget action with name '{{actionName}}'?" + "data": "Dati", + "settings": "Iestatījumi", + "advanced": "Paaugstināta līmeņa", + "title": "Virsraksts", + "general-settings": "Pamata iestatījumi", + "display-title": "Rādīt virsrakstu", + "drop-shadow": "Nomest ēnas", + "enable-fullscreen": "Iespējot pilnekrānu", + "background-color": "Fona krāsa", + "text-color": "Teksta krāsa", + "padding": "Polsterējums", + "margin": "Robežas", + "widget-style": "Logrīka stils", + "title-style": "Virsraksta stils", + "mobile-mode-settings": "Mobilās modes iestatījumi", + "order": "Pasūtīt", + "height": "Augstums", + "units": "Papildus simbols vērtības atrādīšanai", + "decimals": "Ciparu skaits pēc komata", + "timewindow": "Laika logs", + "use-dashboard-timewindow": "Lietot paneļa laika logu", + "display-timewindow": "Rādīt laika logu", + "display-legend": "Rādīt leģendu", + "datasources": "Datu avoti", + "maximum-datasources": "Maksimums { count, plural, 1 {1 datasource is allowed.} other {# datu avoti atļautie} }", + "datasource-type": "Tips", + "datasource-parameters": "Parametri", + "remove-datasource": "Noņemt datu avotu", + "add-datasource": "Pievienot datu avotu", + "target-device": "Mērķa iekārta", + "alarm-source": "Trauksmes avots", + "actions": "Aktivitātes", + "action": "Aktivitātes", + "add-action": "Pievienot aktivitāti", + "search-actions": "Meklēt aktivitātes", + "action-source": "Aktivitāšu avots", + "action-source-required": "Aktivitāšu avoti ir nepieciešami.", + "action-name": "Nosaukums", + "action-name-required": "Aktitiāšu nosaukums ir nepieciešams.", + "action-name-not-unique": "Cita aktivitāte ar tādu pašu nosaukumu jau eksistē.
Aktitivātes nosaukumam ir jābūt unikālam vienā aktivitātes avotā.", + "action-icon": "Ikona", + "action-type": "Tips", + "action-type-required": "Aktivitātes tips ir nepieciešams.", + "edit-action": "Rediģēt aktivitāti", + "delete-action": "Dzēst aktivitāti", + "delete-action-title": "Dzēst logrīka aktivitāti", + "delete-action-text": "Vai esat pārliecināts, ka vēlaties dzēst logrīka aktivitāti ar nosaukumu '{{actionName}}'?" }, "widget-type": { - "import": "Import widget type", - "export": "Export widget type", - "export-failed-error": "Unable to export widget type: {{error}}", - "create-new-widget-type": "Create new widget type", - "widget-type-file": "Widget type file", - "invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure." + "import": "Importēt logrīka tipu", + "export": "Eksportēt logrīka tipu", + "export-failed-error": "Nav iespējams eksportēt logrīka tipu: {{error}}", + "create-new-widget-type": "Radīt jaunu logrīka tipu", + "widget-type-file": "Logrīka tipa fails", + "invalid-widget-type-file-error": "Nav iespējams importēt logrīka tipu: Invalīda logrīka tipa datu struktūra." }, "widgets": { "date-range-navigator": { "localizationMap": { - "Sun": "Sun", - "Mon": "Mon", - "Tue": "Tue", - "Wed": "Wed", - "Thu": "Thu", - "Fri": "Fri", - "Sat": "Sat", - "Jan": "Jan", - "Feb": "Feb", - "Mar": "Mar", - "Apr": "Apr", - "May": "May", - "Jun": "Jun", - "Jul": "Jul", - "Aug": "Aug", - "Sep": "Sep", - "Oct": "Oct", - "Nov": "Nov", - "Dec": "Dec", - "January": "January", - "February": "February", - "March": "March", - "April": "April", - "June": "June", - "July": "July", - "August": "August", - "September": "September", - "October": "October", - "November": "November", - "December": "December", - "Custom Date Range": "Custom Date Range", - "Date Range Template": "Date Range Template", - "Today": "Today", - "Yesterday": "Yesterday", - "This Week": "This Week", - "Last Week": "Last Week", - "This Month": "This Month", - "Last Month": "Last Month", - "Year": "Year", - "This Year": "This Year", - "Last Year": "Last Year", - "Date picker": "Date picker", - "Hour": "Hour", - "Day": "Day", - "Week": "Week", - "2 weeks": "2 Weeks", - "Month": "Month", - "3 months": "3 Months", - "6 months": "6 Months", - "Custom interval": "Custom interval", - "Interval": "Interval", - "Step size": "Step size", + "Sun": "Svētdiena", + "Mon": "Pirmdiena", + "Tue": "Otrdiena", + "Wed": "Trešdiena", + "Thu": "Ceturdiena", + "Fri": "Piekdiena", + "Sat": "Sestdiena", + "Jan": "Janvāris", + "Feb": "Februāris", + "Mar": "Marts", + "Apr": "Aprīlis", + "May": "Maijs", + "Jun": "Jūnijs", + "Jul": "Jūlijs", + "Aug": "Augusts", + "Sep": "Septembris", + "Oct": "Oktobris", + "Nov": "Novembris", + "Dec": "Decembris", + "January": "Janvāris", + "February": "Februāris", + "March": "Marts", + "April": "Aprīlis", + "June": "Jūnijs", + "July": "Jūlijs", + "August": "Augusts", + "September": "Septembris", + "October": "Oktobris", + "November": "Novembris", + "December": "Decembris", + "Custom Date Range": "Lietotāja datu diapazons", + "Date Range Template": "Datu diapazona templeits", + "Today": "Šodien", + "Yesterday": "Vakardien", + "This Week": "Šī nedēļa", + "Last Week": "Pēdējā nedēļa", + "This Month": "Šis mēnesis", + "Last Month": "Pēdējais mēnesis", + "Year": "Gads", + "This Year": "Šis gads", + "Last Year": "Pagājušais gads", + "Date picker": "Datu atlasītājs", + "Hour": "Stunda", + "Day": "Diena", + "Week": "Nedēļa", + "2 weeks": "2 Nedēļas", + "Month": "Mēnesis", + "3 months": "3 Mēneši", + "6 months": "6 Mēneši", + "Custom interval": "Klienta intervāls", + "Interval": "Intervāls", + "Step size": "Soļa lielums", "Ok": "Ok" } } }, "icon": { - "icon": "Icon", - "select-icon": "Select icon", - "material-icons": "Material icons", - "show-all": "Show all icons" + "icon": "Ikona", + "select-icon": "Atlasīt ikonas", + "material-icons": "Materiālu ikonas", + "show-all": "Rādīt visas ikonas" }, "custom": { "widget-action": { - "action-cell-button": "Action cell button", - "row-click": "On row click", - "polygon-click": "On polygon click", - "marker-click": "On marker click", - "tooltip-tag-action": "Tooltip tag action", - "node-selected": "On node selected", - "element-click": "On HTML element click" + "action-cell-button": "Aktivitātes šunas poga", + "row-click": "Uz rindas klikšķis", + "polygon-click": "Uz daudzstūra klikšķis", + "marker-click": "Uz marķiera klikšķis", + "tooltip-tag-action": "Rīku padomu darbība", + "node-selected": "Uz atlasīto nodi", + "element-click": "HTML elementa klikšķis" } }, "language": { - "language": "Language" + "language": "Valodas" } } From 9d212bf39e669ed4850bbd724d3fabdc2bd7706c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 17:08:43 +0200 Subject: [PATCH 215/261] fixed: infinite loop caused by default md-dialog resize function in Safari --- ui/src/app/dashboard/states/manage-dashboard-states.tpl.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html index 82bd07ba6d..23b2433d0a 100644 --- a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html +++ b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html @@ -15,7 +15,7 @@ limitations under the License. --> - +
From 37173bba100fe733911cfe19e9883aa8b4a6f981 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Wed, 19 Feb 2020 17:12:22 +0200 Subject: [PATCH 216/261] Features/flot thresholds (#2412) * Thresholds draft * Make generated thresholds unique * Code refactoring --- ui/src/app/widget/lib/flot-widget.js | 210 ++++++++++++++++++++++++++- 1 file changed, 205 insertions(+), 5 deletions(-) diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index de3afcff33..3be077ca6d 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -33,7 +33,8 @@ export default class TbFlot { this.ctx = ctx; this.chartType = chartType || 'line'; var settings = ctx.settings; - var utils = this.ctx.$scope.$injector.get('utils'); + this.utils = this.ctx.$scope.$injector.get('utils'); + this.types = this.ctx.$scope.$injector.get('types'); ctx.tooltip = $('#flot-series-tooltip'); if (ctx.tooltip.length === 0) { @@ -242,7 +243,8 @@ export default class TbFlot { grid: { hoverable: true, mouseActiveRadius: 10, - autoHighlight: ctx.tooltipIndividual === true + autoHighlight: ctx.tooltipIndividual === true, + markings: [] }, selection : { mode : ctx.isMobile ? null : 'x' }, legend : { @@ -264,7 +266,7 @@ export default class TbFlot { }; if (settings.xaxis) { 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.label = this.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"; @@ -301,7 +303,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 = utils.customTranslation(settings.yaxis.title, settings.yaxis.title) || null; + this.yaxis.label = this.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"; @@ -364,7 +366,7 @@ export default class TbFlot { return ''; }; } - xaxis.label = utils.customTranslation(settings.xaxisSecond.title, settings.xaxisSecond.title) || null; + xaxis.label = this.utils.customTranslation(settings.xaxisSecond.title, settings.xaxisSecond.title) || null; xaxis.position = settings.xaxisSecond.axisPosition; } xaxis.tickLength = 0; @@ -390,6 +392,10 @@ export default class TbFlot { } } + if (this.chartType === 'line' && isFinite(settings.thresholdsLineWidth)) { + options.grid.markingsLineWidth = settings.thresholdsLineWidth; + } + if (this.chartType === 'bar') { options.series.lines = { show: false, @@ -471,6 +477,7 @@ export default class TbFlot { var colors = []; this.yaxes = []; var yaxesMap = {}; + let predefinedThresholds = [], thresholdsDatasources = []; var tooltipValueFormatFunction = null; if (this.ctx.settings.tooltipValueFormatter && this.ctx.settings.tooltipValueFormatter.length) { @@ -485,6 +492,7 @@ export default class TbFlot { var series = this.subscription.data[i]; colors.push(series.dataKey.color); var keySettings = series.dataKey.settings; + series.dataKey.tooltipValueFormatFunction = tooltipValueFormatFunction; if (keySettings.tooltipValueFormatter && keySettings.tooltipValueFormatter.length) { try { @@ -570,9 +578,58 @@ export default class TbFlot { series.yaxis = series.yaxisIndex+1; yaxis.keysInfo[i] = {hidden: false}; yaxis.show = true; + + if (keySettings.thresholds && keySettings.thresholds.length) { + for (let j = 0; j < keySettings.thresholds.length; j++) { + let threshold = keySettings.thresholds[j]; + if (threshold.thresholdValueSource === 'predefinedValue' && isFinite(threshold.thresholdValue)) { + let colorIndex = this.subscription.data.length + predefinedThresholds.length; + this.generateThreshold(predefinedThresholds, series.yaxis, threshold.lineWidth, threshold.color, colorIndex, threshold.thresholdValue); + } else if (threshold.thresholdEntityAlias && threshold.thresholdAttribute) { + let entityAliasId = this.ctx.aliasController.getEntityAliasId(threshold.thresholdEntityAlias); + if (!entityAliasId) { + continue; + } + + let datasource = thresholdsDatasources.filter((datasource) => { + return datasource.entityAliasId === entityAliasId; + })[0]; + + let dataKey = { + type: this.types.dataKeyType.attribute, + name: threshold.thresholdAttribute, + label: threshold.thresholdAttribute, + settings: { + yaxis: series.yaxis, + lineWidth: threshold.lineWidth, + color: threshold.color + }, + _hash: Math.random() + }; + + if (datasource) { + datasource.dataKeys.push(dataKey); + } else { + datasource = { + type: this.types.datasourceType.entity, + name: threshold.thresholdEntityAlias, + aliasName: threshold.thresholdEntityAlias, + entityAliasId: entityAliasId, + dataKeys: [ dataKey ] + }; + thresholdsDatasources.push(datasource); + } + } + } + } } } + this.subscribeForThresholdsAttributes(thresholdsDatasources); + + this.options.grid.markings = predefinedThresholds; + this.predefinedThresholds = predefinedThresholds; + this.options.colors = colors; this.options.yaxes = angular.copy(this.yaxes); if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { @@ -657,6 +714,74 @@ export default class TbFlot { return yaxis; } + subscribeForThresholdsAttributes(datasources) { + let tbFlot = this; + let thresholdsSourcesSubscriptionOptions = { + datasources: datasources, + useDashboardTimewindow: false, + type: this.types.widgetType.latest.value, + callbacks: { + onDataUpdated: (subscription) => {tbFlot.thresholdsSourcesDataUpdated(subscription.data)} + } + }; + this.ctx.subscriptionApi.createSubscription(thresholdsSourcesSubscriptionOptions, true).then( + (subscription) => { + tbFlot.thresholdsSourcesSubscription = subscription; + } + ); + } + + thresholdsSourcesDataUpdated(data) { + let allThresholds = angular.copy(this.predefinedThresholds); + for (let i = 0; i < data.length; i++) { + let keyData = data[i]; + if (keyData && keyData.data && keyData.data[0]) { + let attrValue = keyData.data[0][1]; + if (isFinite(attrValue)) { + let settings = keyData.dataKey.settings; + let colorIndex = this.subscription.data.length + allThresholds.length; + this.generateThreshold(allThresholds, settings.yaxis, settings.lineWidth, settings.color, colorIndex, attrValue); + } + } + } + + this.options.grid.markings = allThresholds; + this.redrawPlot(); + } + + generateThreshold(existingThresholds, yaxis, lineWidth, color, defaultColorIndex, thresholdValue) { + let marking = {}; + let markingYAxis; + + if (yaxis !== 1) { + markingYAxis = 'y' + yaxis + 'axis'; + } else { + markingYAxis = 'yaxis'; + } + + if (isFinite(lineWidth)) { + marking.lineWidth = lineWidth; + } + + if (angular.isDefined(color)) { + marking.color = color; + } else { + marking.color = this.utils.getMaterialColor(defaultColorIndex); + } + + marking[markingYAxis] = { + from: thresholdValue, + to: thresholdValue + }; + + let similarMarkings = existingThresholds.filter((existingMarking) => { + return angular.equals(existingMarking[markingYAxis], marking[markingYAxis]); + }); + if (!similarMarkings.length) { + existingThresholds.push(marking); + } + } + update() { if (this.updateTimeoutHandle) { this.ctx.$scope.$timeout.cancel(this.updateTimeoutHandle); @@ -982,6 +1107,12 @@ export default class TbFlot { "default": "left" }; } + if (chartType === 'graph' || chartType === 'bar') { + properties["thresholdsLineWidth"] = { + "title": "Default line width for all thresholds", + "type": "number" + }; + } properties["shadowSize"] = { "title": "Shadow size", "type": "number", @@ -1151,6 +1282,9 @@ export default class TbFlot { ] }); } + if (chartType === 'graph' || chartType === 'bar') { + schema["form"].push("thresholdsLineWidth"); + } schema["form"].push("shadowSize"); schema["form"].push({ "key": "fontColor", @@ -1456,7 +1590,73 @@ export default class TbFlot { }; var properties = schema["schema"]["properties"]; + if (chartType === 'graph' || chartType === 'bar') { + properties["thresholds"] = { + "title": "Thresholds", + "type": "array", + "items": { + "title": "Threshold", + "type": "object", + "properties": { + "thresholdValueSource": { + "title": "Threshold value source", + "type": "string", + "default": "predefinedValue" + }, + "thresholdEntityAlias": { + "title": "Thresholds source entity alias", + "type": "string" + }, + "thresholdAttribute": { + "title": "Threshold source entity attribute", + "type": "string" + }, + "thresholdValue": { + "title": "Threshold value (if predefined value is selected)", + "type": "number" + }, + "lineWidth": { + "title": "Line width", + "type": "number" + }, + "color": { + "title": "Color", + "type": "string" + } + } + }, + "required": [] + }; + schema["form"].push({ + "key": "thresholds", + "items": [ + { + "key": "thresholds[].thresholdValueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "thresholds[].thresholdValue", + "thresholds[].thresholdEntityAlias", + "thresholds[].thresholdAttribute", + { + "key": "thresholds[].color", + "type": "color" + }, + "thresholds[].lineWidth" + ] + }); + properties["comparisonSettings"] = { "title": "Comparison Settings", "type": "object", From 5113f90a171c692c0cc96118d677c72b89b1de05 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 19 Feb 2020 17:16:09 +0200 Subject: [PATCH 217/261] Fix name gateway widget (#2421) --- .../main/data/json/system/widget_bundles/gateway_widgets.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json index 669f706d15..0158b38fe9 100644 --- a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -32,9 +32,9 @@ "templateHtml": "\n\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.formId = \"form-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onResize = function() {\n self.ctx.$scope.$broadcast('gateway-form-resize', self.ctx.$scope.formId);\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"Gatwey Configuration\"\n },\n \"archiveFileName\": {\n \"title\": \"Default archive file name\",\n \"type\": \"string\",\n \"default\": \"gatewayConfiguration\"\n },\n \"gatewayType\": {\n \"title\": \"Device type for new gateway\",\n \"type\": \"string\",\n \"default\": \"Gateway\"\n },\n \"successfulSave\": {\n \"title\": \"Text message about successfully saved gateway configuration\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"gatewayNameExists\": {\n \"title\": \"Text message when device with entered name is already exists\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n [\n \"widgetTitle\",\n \"archiveFileName\",\n \"gatewayType\"\n ],\n [\n \"successfulSave\",\n \"gatewayNameExists\"\n ]\n ],\n \"groupInfoes\": [{\n \"formIndex\": 0,\n \"GroupTitle\": \"General settings\"\n }, {\n \"formIndex\": 1,\n \"GroupTitle\": \"Messages settings\"\n }]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"Gateway Configuration\"\n },\n \"archiveFileName\": {\n \"title\": \"Default archive file name\",\n \"type\": \"string\",\n \"default\": \"gatewayConfiguration\"\n },\n \"gatewayType\": {\n \"title\": \"Device type for new gateway\",\n \"type\": \"string\",\n \"default\": \"Gateway\"\n },\n \"successfulSave\": {\n \"title\": \"Text message about successfully saved gateway configuration\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"gatewayNameExists\": {\n \"title\": \"Text message when device with entered name is already exists\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n [\n \"widgetTitle\",\n \"archiveFileName\",\n \"gatewayType\"\n ],\n [\n \"successfulSave\",\n \"gatewayNameExists\"\n ]\n ],\n \"groupInfoes\": [{\n \"formIndex\": 0,\n \"GroupTitle\": \"General settings\"\n }, {\n \"formIndex\": 1,\n \"GroupTitle\": \"Messages 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\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"widgetTitle\":\"Gatwey Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":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\":{\"widgetTitle\":\"Gateway Configuration\",\"archiveFileName\":\"configurationGateway\"},\"title\":\"Gateway Configuration\",\"dropShadow\":true,\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } } ] From 755f777b1682bae53c1cd9048995aace0b79f974 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 19:07:32 +0200 Subject: [PATCH 218/261] Fix upgrade --- .../thingsboard/server/install/ThingsboardInstallService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 7ef4363e77..842550d209 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -135,7 +135,9 @@ public class ThingsboardInstallService { case "2.4.3": log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); - databaseTsUpgradeService.upgradeDatabase("2.4.3"); + if (databaseTsUpgradeService != null) { + databaseTsUpgradeService.upgradeDatabase("2.4.3"); + } databaseEntitiesUpgradeService.upgradeDatabase("2.4.3"); log.info("Updating system data..."); From 2a5fffe5f4d5a42a6b94925f57170cbfaffc0d4f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 19 Feb 2020 19:40:42 +0200 Subject: [PATCH 219/261] Fix upgrade --- .../server/service/install/SqlDatabaseUpgradeService.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 d5c01801a0..8c8a7dd759 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 @@ -30,6 +30,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Connection; import java.sql.DriverManager; +import java.sql.SQLSyntaxErrorException; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; @@ -213,6 +214,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService try { conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;"); } catch (Exception e) { + if (e instanceof SQLSyntaxErrorException) { + try { + conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v varchar(10000000);"); + } catch (Exception e1) { + } + } } try { conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration SET DATA TYPE varchar(100000000);"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script From 4cd45e4b3e558c7f464b30ad6940ed4a5d4556f1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 20 Feb 2020 08:13:48 +0200 Subject: [PATCH 220/261] Fix for RestClient.getActivateToken method --- .../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 8b230b1238..f9e26ec28c 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -549,7 +549,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { public String getActivateToken(UserId userId) { String activationLink = getActivationLink(userId); - return StringUtils.delete(activationLink, baseURL + ACTIVATE_TOKEN_REGEX); + return activationLink.substring(activationLink.lastIndexOf(ACTIVATE_TOKEN_REGEX) + ACTIVATE_TOKEN_REGEX.length()); } public Optional getUser() { From 2a58c6f20996407ebeaad488cdcbd9abbfdb5f4d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 20 Feb 2020 10:13:37 +0200 Subject: [PATCH 221/261] fixed RestClient.saveRelation method --- .../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 f9e26ec28c..82c1eb6fd5 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -1139,7 +1139,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public void saveRelation(EntityRelation relation) { - restTemplate.postForLocation(baseURL + "/api/relation", null); + restTemplate.postForLocation(baseURL + "/api/relation", relation); } public void deleteRelation(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup, EntityId toId) { From c17b8cd96c4e7754515ac737503d6c79cd134fd3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 20 Feb 2020 10:15:57 +0200 Subject: [PATCH 222/261] Fix: Add missing groupId to json forms builder function. --- ui/src/app/components/react/json-form-array.jsx | 2 +- ui/src/app/components/react/json-form-fieldset.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/src/app/components/react/json-form-array.jsx b/ui/src/app/components/react/json-form-array.jsx index eb414d8619..1f20c4aa6d 100644 --- a/ui/src/app/components/react/json-form-array.jsx +++ b/ui/src/app/components/react/json-form-array.jsx @@ -131,7 +131,7 @@ class ThingsboardArray extends React.Component { } let forms = this.props.form.items.map(function(form, index){ var copy = this.copyWithIndex(form, i); - return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); + return this.props.builder(copy, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); }.bind(this)); arrays.push(
  • diff --git a/ui/src/app/components/react/json-form-fieldset.jsx b/ui/src/app/components/react/json-form-fieldset.jsx index f668ba774c..4e078d72e2 100644 --- a/ui/src/app/components/react/json-form-fieldset.jsx +++ b/ui/src/app/components/react/json-form-fieldset.jsx @@ -19,7 +19,7 @@ class ThingsboardFieldSet extends React.Component { render() { let forms = this.props.form.items.map(function(form, index){ - return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); + return this.props.builder(form, this.props.groupId, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onIconClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder); }.bind(this)); return ( From 9f0871af7f69e86cd67480d02544ba4772b7b958 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Thu, 20 Feb 2020 10:17:35 +0200 Subject: [PATCH 223/261] Update field alignment on init, not only on resize; prevent dispaying in a row, when fields should be displayed in a column (#2434) --- ui/src/app/widget/lib/multiple-input-widget.js | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index 678c102143..9d93b8f993 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -225,6 +225,7 @@ function MultipleInputWidgetController($q, $scope, $translate, attributeService, if (!vm.isVerticalAlignment && vm.settings.fieldsInRow) { vm.inputWidthSettings = 100 / vm.settings.fieldsInRow + '%'; } + updateWidgetDisplaying(); } function updateDatasources() { From 3f0cd0d5556af5bf0f7f5376a52807f260c2303b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 21 Feb 2020 13:26:03 +0200 Subject: [PATCH 224/261] Reverted change of the dashboard configuration size --- .../server/service/install/SqlDatabaseUpgradeService.java | 4 ---- dao/src/main/resources/sql/schema-entities.sql | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) 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 8c8a7dd759..ef03e3ec43 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 @@ -221,10 +221,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } } } - try { - conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration SET DATA TYPE varchar(100000000);"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - } catch (Exception e) { - } log.info("Schema updated."); } break; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 8d28329047..55893fc124 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -108,7 +108,7 @@ CREATE TABLE IF NOT EXISTS customer ( CREATE TABLE IF NOT EXISTS dashboard ( id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, - configuration varchar(100000000), + configuration varchar(10000000), assigned_customers varchar(1000000), search_text varchar(255), tenant_id varchar(31), From 54b0de0c2c5a4d68eb3cd9ca3965ba2521abf105 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 20 Feb 2020 16:09:34 +0200 Subject: [PATCH 225/261] move rest-client to own module --- msa/black-box-tests/pom.xml | 4 +++ pom.xml | 1 + rest-client/pom.xml | 34 +++++++++++++++++++ .../org/thingsboard/client}/RestClient.java | 4 +-- .../client}/utils/RestJsonConverter.java | 2 +- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 rest-client/pom.xml rename {tools/src/main/java/org/thingsboard/client/tools => rest-client/src/main/java/org/thingsboard/client}/RestClient.java (99%) rename {tools/src/main/java/org/thingsboard/client/tools => rest-client/src/main/java/org/thingsboard/client}/utils/RestJsonConverter.java (98%) diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index f624b7ffd3..ea8ae0db3a 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -90,6 +90,10 @@ org.thingsboard tools + + org.thingsboard + rest-client + diff --git a/pom.xml b/pom.xml index ef05999b58..21bb0a6c69 100755 --- a/pom.xml +++ b/pom.xml @@ -106,6 +106,7 @@ tools application msa + rest-client diff --git a/rest-client/pom.xml b/rest-client/pom.xml new file mode 100644 index 0000000000..8de2e45589 --- /dev/null +++ b/rest-client/pom.xml @@ -0,0 +1,34 @@ + + + + thingsboard + org.thingsboard + 2.5.0-SNAPSHOT + + 4.0.0 + + rest-client + jar + + Thingsboard Rest Client + https://thingsboard.io + + + UTF-8 + ${basedir}/.. + + + + + org.thingsboard.common + data + + + org.springframework.boot + spring-boot-starter-web + + + + \ No newline at end of file diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/rest-client/src/main/java/org/thingsboard/client/RestClient.java similarity index 99% rename from tools/src/main/java/org/thingsboard/client/tools/RestClient.java rename to rest-client/src/main/java/org/thingsboard/client/RestClient.java index 82c1eb6fd5..2c61b12c1c 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/client/RestClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.client.tools; +package org.thingsboard.client; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,7 +31,7 @@ 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.thingsboard.client.tools.utils.RestJsonConverter; +import org.thingsboard.client.utils.RestJsonConverter; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; diff --git a/tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java b/rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java similarity index 98% rename from tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java rename to rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java index 5e70e78659..2470c2d669 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/utils/RestJsonConverter.java +++ b/rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.client.tools.utils; +package org.thingsboard.client.utils; import com.fasterxml.jackson.databind.JsonNode; import org.springframework.util.CollectionUtils; From 30eaa28f6ccdcb889b4044977628bd8fac039b0b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 20 Feb 2020 16:30:24 +0200 Subject: [PATCH 226/261] added rest-client dependencies --- application/pom.xml | 5 ++++ .../server/msa/AbstractContainerTest.java | 2 +- pom.xml | 6 ++++ rest-client/pom.xml | 28 ++++++++++++++----- .../{ => rest}/client/RestClient.java | 4 +-- .../client/utils/RestJsonConverter.java | 2 +- 6 files changed, 36 insertions(+), 11 deletions(-) rename rest-client/src/main/java/org/thingsboard/{ => rest}/client/RestClient.java (99%) rename rest-client/src/main/java/org/thingsboard/{ => rest}/client/utils/RestJsonConverter.java (98%) diff --git a/application/pom.xml b/application/pom.xml index acadf92978..21a319491d 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -220,6 +220,11 @@ tools test + + org.thingsboard + rest-client + test + org.springframework.boot spring-boot-starter-test diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index 1d603a9415..6032dc112a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -38,7 +38,7 @@ import org.junit.rules.TestRule; import org.junit.rules.TestWatcher; import org.junit.runner.Description; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.thingsboard.client.tools.RestClient; +import org.thingsboard.rest.client.RestClient; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; diff --git a/pom.xml b/pom.xml index 21bb0a6c69..547c97448f 100755 --- a/pom.xml +++ b/pom.xml @@ -432,6 +432,12 @@ ${project.version} test + + org.thingsboard + rest-client + ${project.version} + test + org.thingsboard dao diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 8de2e45589..2ed7890e6e 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -1,14 +1,28 @@ - - + + 4.0.0 - thingsboard org.thingsboard 2.5.0-SNAPSHOT + thingsboard - 4.0.0 - rest-client jar @@ -31,4 +45,4 @@ - \ No newline at end of file + diff --git a/rest-client/src/main/java/org/thingsboard/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java similarity index 99% rename from rest-client/src/main/java/org/thingsboard/client/RestClient.java rename to rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 2c61b12c1c..2ece3228aa 100644 --- a/rest-client/src/main/java/org/thingsboard/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.client; +package org.thingsboard.rest.client; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,7 +31,7 @@ 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.thingsboard.client.utils.RestJsonConverter; +import org.thingsboard.rest.client.utils.RestJsonConverter; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; diff --git a/rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java similarity index 98% rename from rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java rename to rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java index 2470c2d669..d6e7ad9e6c 100644 --- a/rest-client/src/main/java/org/thingsboard/client/utils/RestJsonConverter.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.client.utils; +package org.thingsboard.rest.client.utils; import com.fasterxml.jackson.databind.JsonNode; import org.springframework.util.CollectionUtils; From 106bcd8cc02e9536ff447b8b5f9eab81cf04f444 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 21 Feb 2020 13:41:46 +0200 Subject: [PATCH 227/261] Minimize Dependency --- rest-client/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 2ed7890e6e..5d931d7afd 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -40,8 +40,8 @@ data - org.springframework.boot - spring-boot-starter-web + org.springframework + spring-web From f2aefb5570cd54fd47c9a7666a69e8f835ec2992 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 21 Feb 2020 15:46:41 +0200 Subject: [PATCH 228/261] Fix for PostgreSQL Inserts logic --- .../server/dao/sqlts/psql/PsqlInsertTsRepository.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java index caf4528812..00be466027 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java @@ -85,10 +85,10 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements } else { ps.setNull(7, Types.DOUBLE); ps.setNull(12, Types.DOUBLE); - - ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue())); - ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue())); } + + ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue())); + ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue())); } @Override From e207545ce072e6dbf67c2d5f8c2b74982f6bdcf3 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 21 Feb 2020 15:52:46 +0200 Subject: [PATCH 229/261] Improvement TelemetryController, change TsData value to Object --- .../controller/TelemetryController.java | 23 ++++++++++--------- .../server/service/telemetry/TsData.java | 6 ++--- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 86a2457e99..b52abf991e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -182,11 +182,12 @@ public class TelemetryController extends BaseController { @ResponseBody public DeferredResult getLatestTimeseries( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, - @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { + @RequestParam(name = "keys", required = false) String keysStr, + @RequestParam(name = "useStrictType", required = false, defaultValue = "false") Boolean useStrictType) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr)); + (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictType)); } @@ -200,8 +201,8 @@ public class TelemetryController extends BaseController { @RequestParam(name = "endTs") Long endTs, @RequestParam(name = "interval", defaultValue = "0") Long interval, @RequestParam(name = "limit", defaultValue = "100") Integer limit, - @RequestParam(name = "agg", defaultValue = "NONE") String aggStr - ) throws ThingsboardException { + @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, + @RequestParam(name = "useStrictType", required = false, defaultValue = "false") Boolean useStrictType) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> { // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted @@ -209,7 +210,7 @@ public class TelemetryController extends BaseController { List queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg)) .collect(Collectors.toList()); - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result)); + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictType)); }); } @@ -454,14 +455,14 @@ public class TelemetryController extends BaseController { }); } - private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String keys) { + private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String keys, Boolean useStrictType) { ListenableFuture> future; if (StringUtils.isEmpty(keys)) { future = tsService.findAllLatest(user.getTenantId(), entityId); } else { future = tsService.findLatest(user.getTenantId(), entityId, toKeysList(keys)); } - Futures.addCallback(future, getTsKvListCallback(result)); + Futures.addCallback(future, getTsKvListCallback(result, useStrictType)); } private void getAttributeValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String scope, String keys) { @@ -544,7 +545,7 @@ public class TelemetryController extends BaseController { @Override public void onSuccess(List attributes) { List values = attributes.stream().map(attribute -> - new AttributeData(attribute.getLastUpdateTs(), attribute.getKey(), getKvValue(attribute)) + new AttributeData(attribute.getLastUpdateTs(), attribute.getKey(), getKvValue(attribute)) ).collect(Collectors.toList()); logAttributesRead(user, entityId, scope, keyList, null); response.setResult(new ResponseEntity<>(values, HttpStatus.OK)); @@ -559,14 +560,14 @@ public class TelemetryController extends BaseController { }; } - private FutureCallback> getTsKvListCallback(final DeferredResult response) { + private FutureCallback> getTsKvListCallback(final DeferredResult response, Boolean useStrictType) { return new FutureCallback>() { @Override public void onSuccess(List data) { Map> result = new LinkedHashMap<>(); for (TsKvEntry entry : data) { - result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()) - .add(new TsData(entry.getTs(), entry.getValueAsString())); + Object value = useStrictType ? getKvValue(entry) : entry.getValueAsString(); + result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(new TsData(entry.getTs(), value)); } response.setResult(new ResponseEntity<>(result, HttpStatus.OK)); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java index 367a6a6a1d..14b579025c 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java @@ -18,9 +18,9 @@ package org.thingsboard.server.service.telemetry; public class TsData implements Comparable{ private final long ts; - private final String value; + private final Object value; - public TsData(long ts, String value) { + public TsData(long ts, Object value) { super(); this.ts = ts; this.value = value; @@ -30,7 +30,7 @@ public class TsData implements Comparable{ return ts; } - public String getValue() { + public Object getValue() { return value; } From 4b0aae896dde86e6a7f26ea1c06d72a04b24123f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 21 Feb 2020 18:01:15 +0200 Subject: [PATCH 230/261] Rest API support of strict data types in getTelemetry requests --- .../controller/TelemetryController.java | 16 +++++++-------- .../thingsboard/rest/client/RestClient.java | 20 ++++++++++++++----- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index b52abf991e..e534ebf7c4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -183,11 +183,11 @@ public class TelemetryController extends BaseController { public DeferredResult getLatestTimeseries( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr, @RequestParam(name = "keys", required = false) String keysStr, - @RequestParam(name = "useStrictType", required = false, defaultValue = "false") Boolean useStrictType) throws ThingsboardException { + @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictType)); + (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes)); } @@ -202,7 +202,7 @@ public class TelemetryController extends BaseController { @RequestParam(name = "interval", defaultValue = "0") Long interval, @RequestParam(name = "limit", defaultValue = "100") Integer limit, @RequestParam(name = "agg", defaultValue = "NONE") String aggStr, - @RequestParam(name = "useStrictType", required = false, defaultValue = "false") Boolean useStrictType) throws ThingsboardException { + @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, (result, tenantId, entityId) -> { // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted @@ -210,7 +210,7 @@ public class TelemetryController extends BaseController { List queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg)) .collect(Collectors.toList()); - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictType)); + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes)); }); } @@ -455,14 +455,14 @@ public class TelemetryController extends BaseController { }); } - private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String keys, Boolean useStrictType) { + private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String keys, Boolean useStrictDataTypes) { ListenableFuture> future; if (StringUtils.isEmpty(keys)) { future = tsService.findAllLatest(user.getTenantId(), entityId); } else { future = tsService.findLatest(user.getTenantId(), entityId, toKeysList(keys)); } - Futures.addCallback(future, getTsKvListCallback(result, useStrictType)); + Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes)); } private void getAttributeValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String scope, String keys) { @@ -560,13 +560,13 @@ public class TelemetryController extends BaseController { }; } - private FutureCallback> getTsKvListCallback(final DeferredResult response, Boolean useStrictType) { + private FutureCallback> getTsKvListCallback(final DeferredResult response, Boolean useStrictDataTypes) { return new FutureCallback>() { @Override public void onSuccess(List data) { Map> result = new LinkedHashMap<>(); for (TsKvEntry entry : data) { - Object value = useStrictType ? getKvValue(entry) : entry.getValueAsString(); + Object value = useStrictDataTypes ? getKvValue(entry) : entry.getValueAsString(); result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>()).add(new TsData(entry.getTs(), value)); } response.setResult(new ResponseEntity<>(result, HttpStatus.OK)); diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 2ece3228aa..7e0dbf3858 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -98,6 +98,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -1612,31 +1613,40 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public List getLatestTimeseries(EntityId entityId, List keys) { + return getLatestTimeseries(entityId, keys, true); + } + + public List getLatestTimeseries(EntityId entityId, List keys, boolean useStrictDataTypes) { Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}", + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&useStrictDataTypes={useStrictDataTypes}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>>() { }, entityId.getEntityType().name(), entityId.getId().toString(), - listToString(keys)).getBody(); + listToString(keys), + useStrictDataTypes).getBody(); return RestJsonConverter.toTimeseries(timeseries); } - public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, TimePageLink pageLink) { + return getTimeseries(entityId, keys, interval, agg, pageLink, true); + } + + public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, TimePageLink pageLink, boolean useStrictDataTypes) { Map params = new HashMap<>(); - addPageLinkToParam(params, pageLink); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); params.put("keys", listToString(keys)); params.put("interval", interval == null ? "0" : interval.toString()); params.put("agg", agg == null ? "NONE" : agg.name()); + params.put("useStrictDataTypes", Boolean.toString(useStrictDataTypes)); + addPageLinkToParam(params, pageLink); Map> timeseries = restTemplate.exchange( - baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&agg={agg}&" + getUrlParams(pageLink), + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>>() { From 1ce365d8df83521ee64f88375d89f7f1719b96f0 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Mon, 24 Feb 2020 18:24:10 +0200 Subject: [PATCH 231/261] Improvements RestJsonConverter (#2452) * Improvements RestJsonConverter * Refactored RestJsonConverter --- .../BaseRuleChainTransactionService.java | 1 + .../src/main/resources/thingsboard.yml | 4 +-- .../rest/client/utils/RestJsonConverter.java | 26 ++++++++++++++----- 3 files changed, 23 insertions(+), 8 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 eb1d59aea2..1aee532c93 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 @@ -93,6 +93,7 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ TbTransactionTask transactionTask = new TbTransactionTask(msg, onStart, onEnd, onFailure, System.currentTimeMillis() + duration); int queueSize = queue.size(); if (queueSize >= finalQueueSize) { + log.trace("Queue has no space: {}", transactionTask); executeOnFailure(transactionTask.getOnFailure(), "Queue has no space!"); } else { addMsgToQueues(queue, transactionTask); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3aaf47c524..18b77c4f41 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -250,9 +250,9 @@ actors: error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}" transaction: # Size of queues which store messages for transaction rule nodes - queue_size: "${ACTORS_RULE_TRANSACTION_QUEUE_SIZE:20}" + queue_size: "${ACTORS_RULE_TRANSACTION_QUEUE_SIZE:15000}" # Time in milliseconds for transaction to complete - duration: "${ACTORS_RULE_TRANSACTION_DURATION:15000}" + duration: "${ACTORS_RULE_TRANSACTION_DURATION:60000}" statistics: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java index d6e7ad9e6c..65838fb5bd 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java @@ -22,6 +22,7 @@ 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.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; @@ -73,15 +74,28 @@ public class RestJsonConverter { if (!value.isObject()) { if (value.isBoolean()) { return new BooleanDataEntry(key, value.asBoolean()); - } else if (value.isDouble()) { - return new DoubleDataEntry(key, value.asDouble()); - } else if (value.isLong()) { - return new LongDataEntry(key, value.asLong()); - } else { + } else if (value.isNumber()) { + return parseNumericValue(key, value); + } else if (value.isTextual()) { return new StringDataEntry(key, value.asText()); + } else { + throw new RuntimeException(CAN_T_PARSE_VALUE + value); } } else { - throw new RuntimeException(CAN_T_PARSE_VALUE + value); + return new JsonDataEntry(key, value.toString()); + } + } + + private static KvEntry parseNumericValue(String key, JsonNode value) { + if (value.isFloatingPointNumber()) { + return new DoubleDataEntry(key, value.asDouble()); + } else { + try { + long longValue = Long.parseLong(value.toString()); + return new LongDataEntry(key, longValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Big integer values are not supported!"); + } } } } From 7a2b76b8c0d3e50370dcb7dc282bb39fe90f6068 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 24 Feb 2020 18:24:40 +0200 Subject: [PATCH 232/261] SQL DAO Refactoring --- .../server/dao/HsqlTsDaoConfig.java | 8 +- .../server/dao/PsqlTsDaoConfig.java | 8 +- .../server/dao/TimescaleDaoConfig.java | 6 +- .../model/sqlts/hsql/TsKvCompositeKey.java | 39 ---- .../dao/model/sqlts/hsql/TsKvEntity.java | 105 ---------- .../{ => ts}/TimescaleTsKvCompositeKey.java | 4 +- .../{ => ts}/TimescaleTsKvEntity.java | 4 +- .../sqlts/{psql => ts}/TsKvCompositeKey.java | 4 +- .../model/sqlts/{psql => ts}/TsKvEntity.java | 2 +- ...stractChunkedAggregationTimeseriesDao.java | 183 ++++++++++++++++-- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 1 + .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 177 +---------------- .../dao/sqlts/hsql/TsKvHsqlRepository.java | 132 ------------- .../AbstractInsertRepository.java | 4 +- .../{ => insert}/InsertTsRepository.java | 3 +- .../hsql/HsqlInsertTsRepository.java | 10 +- .../latest}/InsertLatestTsRepository.java | 2 +- .../hsql}/HsqlLatestInsertTsRepository.java | 8 +- .../psql}/PsqlLatestInsertTsRepository.java | 8 +- .../psql/PsqlInsertTsRepository.java | 10 +- .../psql/PsqlPartitioningRepository.java | 4 +- .../TimescaleInsertTsRepository.java | 10 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 178 +---------------- .../timescale/AggregationRepository.java | 4 +- .../timescale/TimescaleTimeseriesDao.java | 6 +- .../timescale/TsKvTimescaleRepository.java | 6 +- .../TsKvRepository.java} | 8 +- 27 files changed, 235 insertions(+), 699 deletions(-) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/{ => ts}/TimescaleTsKvCompositeKey.java (94%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/{ => ts}/TimescaleTsKvEntity.java (99%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{psql => ts}/TsKvCompositeKey.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/model/sqlts/{psql => ts}/TsKvEntity.java (98%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/AbstractInsertRepository.java (96%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/InsertTsRepository.java (88%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/hsql/HsqlInsertTsRepository.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert/latest}/InsertLatestTsRepository.java (93%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{latest => insert/latest/hsql}/HsqlLatestInsertTsRepository.java (94%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{latest => insert/latest/psql}/PsqlLatestInsertTsRepository.java (96%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/psql/PsqlInsertTsRepository.java (94%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/psql/PsqlPartitioningRepository.java (95%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{ => insert}/timescale/TimescaleInsertTsRepository.java (92%) rename dao/src/main/java/org/thingsboard/server/dao/sqlts/{psql/TsKvPsqlRepository.java => ts/TsKvRepository.java} (96%) diff --git a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java index cbe8571922..e2519191e9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java @@ -26,12 +26,12 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Configuration @EnableAutoConfiguration -@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest"}) -@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.hsql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) -@EntityScan({"org.thingsboard.server.dao.model.sqlts.hsql", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) +@ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.hsql", "org.thingsboard.server.dao.sqlts.insert.latest.hsql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement @SqlTsDao @HsqlDao public class HsqlTsDaoConfig { -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java index e3caf5e3d3..65f17709ca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java @@ -26,12 +26,12 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Configuration @EnableAutoConfiguration -@ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest"}) -@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) -@EntityScan({"org.thingsboard.server.dao.model.sqlts.psql", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) +@ComponentScan({"org.thingsboard.server.dao.sqlts.psql"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.psql", "org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.latest", "org.thingsboard.server.dao.sqlts.dictionary"}) +@EntityScan({"org.thingsboard.server.dao.model.sqlts.ts", "org.thingsboard.server.dao.model.sqlts.latest", "org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement @SqlTsDao @PsqlDao public class PsqlTsDaoConfig { -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java index 99cea08d7e..19ae98c736 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java @@ -26,12 +26,12 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; @Configuration @EnableAutoConfiguration -@ComponentScan({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.latest"}) -@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.dictionary", "org.thingsboard.server.dao.sqlts.latest"}) +@ComponentScan({"org.thingsboard.server.dao.sqlts.timescale"}) +@EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.insert.timescale", "org.thingsboard.server.dao.sqlts.dictionary", "org.thingsboard.server.dao.sqlts.latest"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale", "org.thingsboard.server.dao.model.sqlts.dictionary", "org.thingsboard.server.dao.model.sqlts.latest"}) @EnableTransactionManagement @TimescaleDBTsDao @PsqlDao public class TimescaleDaoConfig { -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java deleted file mode 100644 index a17d1373b0..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvCompositeKey.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2020 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.model.sqlts.hsql; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.EntityType; - -import javax.persistence.Transient; -import java.io.Serializable; -import java.util.UUID; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class TsKvCompositeKey implements Serializable { - - @Transient - private static final long serialVersionUID = -4089175869616037523L; - - private UUID entityId; - private int key; - private long ts; - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java deleted file mode 100644 index a38dc185a8..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright © 2016-2020 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.model.sqlts.hsql; - -import lombok.Data; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.ToData; -import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.Table; - -import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; - -@Data -@Entity -@Table(name = "ts_kv") -@IdClass(TsKvCompositeKey.class) -public final class TsKvEntity extends AbstractTsKvEntity implements ToData { - - @Id - @Column(name = KEY_COLUMN) - private int key; - - public TsKvEntity() { - } - - public TsKvEntity(String strValue) { - this.strValue = strValue; - } - - public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) { - if (!isAllNull(longValue, doubleValue, longCountValue, doubleCountValue)) { - switch (aggType) { - case AVG: - double sum = 0.0; - if (longValue != null) { - sum += longValue; - } - if (doubleValue != null) { - sum += doubleValue; - } - long totalCount = longCountValue + doubleCountValue; - if (totalCount > 0) { - this.doubleValue = sum / (longCountValue + doubleCountValue); - } else { - this.doubleValue = 0.0; - } - break; - case SUM: - if (doubleCountValue > 0) { - this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0); - } else { - this.longValue = longValue; - } - break; - case MIN: - case MAX: - if (longCountValue > 0 && doubleCountValue > 0) { - this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue()); - } else if (doubleCountValue > 0) { - this.doubleValue = doubleValue; - } else if (longCountValue > 0) { - this.longValue = longValue; - } - break; - } - } - } - - public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) { - if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { - if (booleanValueCount != 0) { - this.longValue = booleanValueCount; - } else if (strValueCount != 0) { - this.longValue = strValueCount; - } else if (jsonValueCount != 0) { - this.longValue = jsonValueCount; - } else { - this.longValue = longValueCount + doubleValueCount; - } - } - } - - @Override - public boolean isNotEmpty() { - return strValue != null || longValue != null || doubleValue != null || booleanValue != null; - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java index 8209b4a77f..e7db0572ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.timescale; +package org.thingsboard.server.dao.model.sqlts.timescale.ts; import lombok.AllArgsConstructor; import lombok.Data; @@ -35,4 +35,4 @@ public class TimescaleTsKvCompositeKey implements Serializable { private UUID entityId; private int key; private long ts; -} \ No newline at end of file +} 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/ts/TimescaleTsKvEntity.java similarity index 99% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java index 3bedae1563..76a95667a9 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/ts/TimescaleTsKvEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.timescale; +package org.thingsboard.server.dao.model.sqlts.timescale.ts; import lombok.Data; import lombok.EqualsAndHashCode; @@ -191,4 +191,4 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToD public boolean isNotEmpty() { return ts != null && (strValue != null || longValue != null || doubleValue != null || booleanValue != null || jsonValue != null); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java index f487b11414..ffc2076ecd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvCompositeKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.psql; +package org.thingsboard.server.dao.model.sqlts.ts; import lombok.AllArgsConstructor; import lombok.Data; @@ -34,4 +34,4 @@ public class TsKvCompositeKey implements Serializable { private UUID entityId; private int key; private long ts; -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java similarity index 98% rename from dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java index b10c5445ca..6d01b62d25 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/ts/TsKvEntity.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sqlts.psql; +package org.thingsboard.server.dao.model.sqlts.ts; import lombok.Data; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index cacf7aea93..3841d4f9c1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -15,32 +15,45 @@ */ package org.thingsboard.server.dao.sqlts; +import com.google.common.util.concurrent.Futures; 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.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueue; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; +import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.ts.TsKvRepository; +import org.thingsboard.server.dao.timeseries.TimeseriesDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j -public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSqlTimeseriesDao { +public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSqlTimeseriesDao implements TimeseriesDao { @Autowired - protected InsertTsRepository insertRepository; + protected TsKvRepository tsKvRepository; - protected TbSqlBlockingQueue> tsQueue; + @Autowired + protected InsertTsRepository insertRepository; + + protected TbSqlBlockingQueue> tsQueue; @PostConstruct protected void init() { @@ -63,9 +76,102 @@ public abstract class AbstractChunkedAggregationTimeseriesDao> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation); + @Override + public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return service.submit(() -> { + tsKvRepository.delete( + entityId.getId(), + getOrSaveKeyId(query.getKey()), + query.getStartTs(), + query.getEndTs()); + return null; + }); + } + + @Override + public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { + return getSaveLatestFuture(entityId, tsKvEntry); + } + + @Override + public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return getRemoveLatestFuture(tenantId, entityId, query); + } + + @Override + public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { + return getFindLatestFuture(entityId, key); + } + + @Override + public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { + return getFindAllLatestFuture(entityId); + } + + @Override + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + return Futures.immediateFuture(null); + } + + @Override + public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + return Futures.immediateFuture(null); + } + + @Override + public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { + return processFindAllAsync(tenantId, entityId, queries); + } + + @Override + protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + if (query.getAggregation() == Aggregation.NONE) { + return findAllAsyncWithLimit(tenantId, entityId, query); + } else { + long stepTs = query.getStartTs(); + List>> futures = new ArrayList<>(); + while (stepTs < query.getEndTs()) { + long startTs = stepTs; + long endTs = stepTs + query.getInterval(); + long ts = startTs + (endTs - startTs) / 2; + futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); + stepTs = endTs; + } + return getTskvEntriesFuture(Futures.allAsList(futures)); + } + } + + @Override + protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + Integer keyId = getOrSaveKeyId(query.getKey()); + List tsKvEntities = tsKvRepository.findAllWithLimit( + entityId.getId(), + keyId, + query.getStartTs(), + query.getEndTs(), + new PageRequest(0, query.getLimit(), + new Sort(Sort.Direction.fromString( + query.getOrderBy()), "ts"))); + tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); + return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); + } - protected void switchAggregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + List> entitiesFutures = new ArrayList<>(); + switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); + return Futures.transform(setFutures(entitiesFutures), entity -> { + if (entity != null && entity.isNotEmpty()) { + entity.setEntityId(entityId.getId()); + entity.setStrKey(key); + entity.setTs(ts); + return Optional.of(DaoUtil.getData(entity)); + } else { + return Optional.empty(); + } + }); + } + + protected void switchAggregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { switch (aggregation) { case AVG: findAvg(tenantId, entityId, key, startTs, endTs, entitiesFutures); @@ -87,19 +193,64 @@ public abstract class AbstractChunkedAggregationTimeseriesDao> entitiesFutures); + protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findCount( + entityId.getId(), + keyId, + startTs, + endTs)); + } - protected abstract void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findSum( + entityId.getId(), + keyId, + startTs, + endTs)); + } - protected abstract void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findStringMin( + entityId.getId(), + keyId, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMin( + entityId.getId(), + keyId, + startTs, + endTs)); + } - protected abstract void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findStringMax( + entityId.getId(), + keyId, + startTs, + endTs)); + entitiesFutures.add(tsKvRepository.findNumericMax( + entityId.getId(), + keyId, + startTs, + endTs)); + } - protected abstract void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures); + protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + Integer keyId = getOrSaveKeyId(key); + entitiesFutures.add(tsKvRepository.findAvg( + entityId.getId(), + keyId, + startTs, + endTs)); + } - protected SettableFuture setFutures(List> entitiesFutures) { - SettableFuture listenableFuture = SettableFuture.create(); - CompletableFuture> entities = + protected SettableFuture setFutures(List> entitiesFutures) { + SettableFuture listenableFuture = SettableFuture.create(); + CompletableFuture> entities = CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()])) .thenApply(v -> entitiesFutures.stream() .map(CompletableFuture::join) @@ -109,8 +260,8 @@ public abstract class AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { - - @Autowired - private TsKvHsqlRepository tsKvRepository; - - @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { - return processFindAllAsync(tenantId, entityId, queries); - } +public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { @@ -72,154 +51,4 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa return tsQueue.add(new EntityContainer(entity, null)); } - @Override - public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> { - tsKvRepository.delete( - entityId.getId(), - getOrSaveKeyId(query.getKey()), - query.getStartTs(), - query.getEndTs()); - return null; - }); - } - - @Override - public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - return getSaveLatestFuture(entityId, tsKvEntry); - } - - @Override - public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return getRemoveLatestFuture(tenantId, entityId, query); - } - - @Override - public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { - return getFindLatestFuture(entityId, key); - } - - @Override - public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { - return getFindAllLatestFuture(entityId); - } - - @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return Futures.immediateFuture(null); - } - - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return Futures.immediateFuture(null); - } - - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(tenantId, entityId, query); - } else { - long stepTs = query.getStartTs(); - List>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + query.getInterval(); - long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); - stepTs = endTs; - } - return getTskvEntriesFuture(Futures.allAsList(futures)); - } - } - - @Override - protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - List tsKvEntities = tsKvRepository.findAllWithLimit( - entityId.getId(), - getOrSaveKeyId(query.getKey()), - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), "ts"))); - tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); - return Futures.immediateFuture( - DaoUtil.convertDataList( - tsKvEntities)); - } - - @Override - protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - List> entitiesFutures = new ArrayList<>(); - switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); - return Futures.transform(setFutures(entitiesFutures), entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityId.getId()); - entity.setKey(getOrSaveKeyId(key)); - entity.setTs(ts); - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); - } - }); - } - - @Override - protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findCount( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findSum( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMin( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMin( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMax( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMax( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findAvg( - entityId.getId(), - keyId, - startTs, - endTs)); - } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java deleted file mode 100644 index a7c0effb97..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java +++ /dev/null @@ -1,132 +0,0 @@ -/** - * Copyright © 2016-2020 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.hsql; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.query.Param; -import org.springframework.scheduling.annotation.Async; -import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.hsql.TsKvCompositeKey; -import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; - -@SqlDao -public interface TsKvHsqlRepository extends CrudRepository { - - @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - List findAllWithLimit(@Param("entityId") UUID entityId, - @Param("entityKey") int key, - @Param("startTs") long startTs, - @Param("endTs") long endTs, - Pageable pageable); - - @Transactional - @Modifying - @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - void delete(@Param("entityId") UUID entityId, - @Param("entityKey") int key, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " + - "WHERE tskv.strValue IS NOT NULL AND tskv.entityId = :entityId AND tskv.key = :entityKey" + - " AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findStringMax(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " + - "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " + - "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'MAX') FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findNumericMax(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - - @Async - @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " + - "WHERE tskv.strValue IS NOT NULL AND tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findStringMin(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " + - "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " + - "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'MIN') FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findNumericMin(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + - "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findCount(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + - "SUM(COALESCE(tskv.doubleValue, 0.0)), " + - "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'AVG') FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findAvg(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - - @Async - @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " + - "SUM(COALESCE(tskv.doubleValue, 0.0)), " + - "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + - "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " + - "'SUM') FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") - CompletableFuture findSum(@Param("entityId") UUID entityId, - @Param("entityKey") int entityKey, - @Param("startTs") long startTs, - @Param("endTs") long endTs); - -} \ 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/insert/AbstractInsertRepository.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/AbstractInsertRepository.java index 7c40ad8421..7116df9fb3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/AbstractInsertRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts; +package org.thingsboard.server.dao.sqlts.insert; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -44,4 +44,4 @@ public abstract class AbstractInsertRepository { } return strValue; } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/InsertTsRepository.java similarity index 88% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/InsertTsRepository.java index 6ab11618f0..a2c066322a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/InsertTsRepository.java @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts; +package org.thingsboard.server.dao.sqlts.insert; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; +import org.thingsboard.server.dao.sqlts.EntityContainer; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java index d1e5294309..189758a947 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/hsql/HsqlInsertTsRepository.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.hsql; +package org.thingsboard.server.dao.sqlts.insert.hsql; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.hsql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.EntityContainer; -import org.thingsboard.server.dao.sqlts.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @@ -86,4 +86,4 @@ public class HsqlInsertTsRepository extends AbstractInsertRepository implements } }); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/InsertLatestTsRepository.java similarity index 93% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/InsertLatestTsRepository.java index c7b0f68b7e..539ce2d6f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/InsertLatestTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/InsertLatestTsRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts; +package org.thingsboard.server.dao.sqlts.insert.latest; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java index 65ac6257f0..224dc52805 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/hsql/HsqlLatestInsertTsRepository.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.latest; +package org.thingsboard.server.dao.sqlts.insert.latest.hsql; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; -import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; -import org.thingsboard.server.dao.sqlts.InsertLatestTsRepository; +import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @@ -82,4 +82,4 @@ public class HsqlLatestInsertTsRepository extends AbstractInsertRepository imple } }); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java index d367f44620..41abae52f8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.latest; +package org.thingsboard.server.dao.sqlts.insert.latest.psql; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; @@ -21,8 +21,8 @@ import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; -import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; -import org.thingsboard.server.dao.sqlts.InsertLatestTsRepository; +import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; +import org.thingsboard.server.dao.sqlts.insert.latest.InsertLatestTsRepository; import org.thingsboard.server.dao.util.PsqlTsAnyDao; import java.sql.PreparedStatement; @@ -151,4 +151,4 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple } }); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlInsertTsRepository.java similarity index 94% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlInsertTsRepository.java index 00be466027..d4a5dd25b0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlInsertTsRepository.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.psql; +package org.thingsboard.server.dao.sqlts.insert.psql; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; +import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; import org.thingsboard.server.dao.sqlts.EntityContainer; -import org.thingsboard.server.dao.sqlts.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @@ -101,4 +101,4 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements private String getInsertOrUpdateQuery(String partitionDate) { return INSERT_INTO_TS_KV + partitionDate + VALUES_ON_CONFLICT_DO_UPDATE; } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlPartitioningRepository.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlPartitioningRepository.java index 0e22cb26ba..a3def06a4d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlPartitioningRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/psql/PsqlPartitioningRepository.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.psql; +package org.thingsboard.server.dao.sqlts.insert.psql; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @@ -38,4 +38,4 @@ public class PsqlPartitioningRepository { .executeUpdate(); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java similarity index 92% rename from dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java index 6d863af105..4cd0a4ab59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sqlts.timescale; +package org.thingsboard.server.dao.sqlts.insert.timescale; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; -import org.thingsboard.server.dao.sqlts.AbstractInsertRepository; +import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; +import org.thingsboard.server.dao.sqlts.insert.AbstractInsertRepository; import org.thingsboard.server.dao.sqlts.EntityContainer; -import org.thingsboard.server.dao.sqlts.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @@ -89,4 +89,4 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem } }); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 6d60e843ec..29cdd64918 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -15,27 +15,20 @@ */ package org.thingsboard.server.dao.sqlts.psql; -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.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.Aggregation; -import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; -import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sqlts.psql.TsKvEntity; +import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.EntityContainer; +import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository; import org.thingsboard.server.dao.timeseries.PsqlPartition; import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; -import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; @@ -44,11 +37,8 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; @@ -59,14 +49,11 @@ import static org.thingsboard.server.dao.timeseries.SqlTsPartitionDate.EPOCH_STA @Slf4j @SqlTsDao @PsqlDao -public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { +public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao { private final Map partitions = new ConcurrentHashMap<>(); private static final ReentrantLock partitionCreationLock = new ReentrantLock(); - @Autowired - private TsKvPsqlRepository tsKvRepository; - @Autowired private PsqlPartitioningRepository partitioningRepository; @@ -110,163 +97,6 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate())); } - @Override - public ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> { - String strKey = query.getKey(); - Integer keyId = getOrSaveKeyId(strKey); - tsKvRepository.delete( - entityId.getId(), - keyId, - query.getStartTs(), - query.getEndTs()); - return null; - }); - } - - @Override - public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return getRemoveLatestFuture(tenantId, entityId, query); - } - - @Override - public ListenableFuture saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { - return getSaveLatestFuture(entityId, tsKvEntry); - } - - @Override - public ListenableFuture findLatest(TenantId tenantId, EntityId entityId, String key) { - return getFindLatestFuture(entityId, key); - } - - @Override - public ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId) { - return getFindAllLatestFuture(entityId); - } - - @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return Futures.immediateFuture(null); - } - - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return Futures.immediateFuture(null); - } - - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(tenantId, entityId, query); - } else { - long stepTs = query.getStartTs(); - List>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + query.getInterval(); - long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); - stepTs = endTs; - } - return getTskvEntriesFuture(Futures.allAsList(futures)); - } - } - - @Override - protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { - Integer keyId = getOrSaveKeyId(query.getKey()); - List tsKvEntities = tsKvRepository.findAllWithLimit( - entityId.getId(), - keyId, - query.getStartTs(), - query.getEndTs(), - new PageRequest(0, query.getLimit(), - new Sort(Sort.Direction.fromString( - query.getOrderBy()), "ts"))); - tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); - return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); - } - - @Override - protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { - List> entitiesFutures = new ArrayList<>(); - switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); - return Futures.transform(setFutures(entitiesFutures), entity -> { - if (entity != null && entity.isNotEmpty()) { - entity.setEntityId(entityId.getId()); - entity.setStrKey(key); - entity.setTs(ts); - return Optional.of(DaoUtil.getData(entity)); - } else { - return Optional.empty(); - } - }); - } - - @Override - protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findCount( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findSum( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMin( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMin( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findStringMax( - entityId.getId(), - keyId, - startTs, - endTs)); - entitiesFutures.add(tsKvRepository.findNumericMax( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { - Integer keyId = getOrSaveKeyId(key); - entitiesFutures.add(tsKvRepository.findAvg( - entityId.getId(), - keyId, - startTs, - endTs)); - } - - @Override - public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { - return processFindAllAsync(tenantId, entityId, queries); - } - private void savePartition(PsqlPartition psqlPartition) { if (!partitions.containsKey(psqlPartition.getStart())) { partitionCreationLock.lock(); @@ -306,4 +136,4 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa private static long toMills(LocalDateTime time) { return time.toInstant(ZoneOffset.UTC).toEpochMilli(); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index 5a0b9c6a59..bb0b13f2a6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sqlts.timescale; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; +import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; import org.thingsboard.server.dao.util.TimescaleDBTsDao; import javax.persistence.EntityManager; @@ -98,4 +98,4 @@ public class AggregationRepository { } -} \ No newline at end of file +} 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 9f8f5c6f74..176bc712e8 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 @@ -31,12 +31,12 @@ import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; +import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; 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.EntityContainer; -import org.thingsboard.server.dao.sqlts.InsertTsRepository; +import org.thingsboard.server.dao.sqlts.insert.InsertTsRepository; import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @@ -286,4 +286,4 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements startTs, endTs); } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index a4b15abd26..fb9cb6f7fe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -21,8 +21,8 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvCompositeKey; -import org.thingsboard.server.dao.model.sqlts.timescale.TimescaleTsKvEntity; +import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvCompositeKey; +import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; import org.thingsboard.server.dao.util.TimescaleDBTsDao; import java.util.List; @@ -54,4 +54,4 @@ public interface TsKvTimescaleRepository extends CrudRepository { +public interface TsKvRepository extends CrudRepository { @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") From 57eeff3c12a41b3f509997fc8433c7cd20ecdea1 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 26 Feb 2020 16:54:46 +0200 Subject: [PATCH 233/261] Stats interval fixes --- .../java/org/thingsboard/server/actors/ActorSystemContext.java | 2 +- .../server/service/script/AbstractNashornJsInvokeService.java | 2 +- 2 files changed, 2 insertions(+), 2 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 cd51e9c6bc..bf06a3a8ac 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -295,7 +295,7 @@ public class ActorSystemContext { @Getter private final AtomicInteger jsInvokeFailuresCount = new AtomicInteger(0); - @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") + @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}") public void printStats() { if (statisticsEnabled) { if (jsInvokeRequestsCount.get() > 0 || jsInvokeResponsesCount.get() > 0 || jsInvokeFailuresCount.get() > 0) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 6e7d2824f4..e4901785fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -70,7 +70,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Value("${js.local.stats.enabled:false}") private boolean statsEnabled; - @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}") + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") public void printStats() { if (statsEnabled) { int pushedMsgs = jsPushedMsgs.getAndSet(0); From e9befd0a50941927269138f28ecca4da62ad4ed4 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 Feb 2020 09:58:29 +0200 Subject: [PATCH 234/261] Docker - update postgres container configuration --- docker/docker-compose.postgres.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index ae615ef636..d47cbd6c37 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -19,11 +19,12 @@ version: '2.2' services: postgres: restart: always - image: "postgres:10" + image: "postgres:11.6" ports: - "5432" environment: POSTGRES_DB: thingsboard + POSTGRES_PASSWORD: postgres volumes: - ./tb-node/postgres:/var/lib/postgresql/data tb1: From 771bd70a339053a8fefb083e7feba56ae5258c42 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 27 Feb 2020 19:12:54 +0200 Subject: [PATCH 235/261] Fix tb-postgres docker image. Update postgreSQL version to 11 --- msa/tb/docker-postgres/Dockerfile | 6 +++++- msa/tb/docker-postgres/start-db.sh | 6 ++++-- msa/tb/docker-postgres/stop-db.sh | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 297bad0753..a2a24442e4 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -17,7 +17,11 @@ FROM thingsboard/openjdk8 RUN apt-get update -RUN apt-get install -y postgresql postgresql-contrib +RUN apt-get install -y curl +RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null +RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - +RUN apt-get update +RUN apt-get install -y postgresql-11 RUN update-rc.d postgresql disable RUN mkdir -p /var/log/postgres diff --git a/msa/tb/docker-postgres/start-db.sh b/msa/tb/docker-postgres/start-db.sh index e7b873fe83..dfbfc1dd68 100644 --- a/msa/tb/docker-postgres/start-db.sh +++ b/msa/tb/docker-postgres/start-db.sh @@ -17,13 +17,15 @@ firstlaunch=${DATA_FOLDER}/.firstlaunch +export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl) + if [ ! -d ${PGDATA} ]; then mkdir -p ${PGDATA} chown -R postgres:postgres ${PGDATA} - su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl initdb -U postgres' + su postgres -c '${PG_CTL} initdb -U postgres' fi -su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl -l /var/log/postgres/postgres.log -w start' +su postgres -c '${PG_CTL} -l /var/log/postgres/postgres.log -w start' if [ ! -f ${firstlaunch} ]; then su postgres -c 'psql -U postgres -d postgres -c "CREATE DATABASE thingsboard"' diff --git a/msa/tb/docker-postgres/stop-db.sh b/msa/tb/docker-postgres/stop-db.sh index fc5cb1784c..66596d13c8 100644 --- a/msa/tb/docker-postgres/stop-db.sh +++ b/msa/tb/docker-postgres/stop-db.sh @@ -15,4 +15,6 @@ # limitations under the License. # -su postgres -c '/usr/lib/postgresql/10/bin/pg_ctl stop' +export PG_CTL=$(find /usr/lib/postgresql/ -name pg_ctl) + +su postgres -c '${PG_CTL} stop' From 96147bff8f2a17b5feb41bab9943f99ee491836a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 28 Feb 2020 14:13:12 +0200 Subject: [PATCH 236/261] Fix PreAuthorize annotation for claimDevice REST method. --- .../org/thingsboard/server/controller/DeviceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 828f75bd47..d147d27b40 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -390,7 +390,7 @@ public class DeviceController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('CUSTOMER_USER')") + @PreAuthorize("hasAuthority('CUSTOMER_USER')") @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) @ResponseBody public DeferredResult claimDevice(@PathVariable(DEVICE_NAME) String deviceName, From 7de309e217087a866c1ed0d03bb43e88bad56338 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 27 Feb 2020 16:06:44 +0200 Subject: [PATCH 237/261] Created HsqlEntityDatabaseSchemaService, improvement TimescaleTsDatabaseUpgradeService and TimescaleInsertTsRepository --- .../HsqlEntityDatabaseSchemaService.java | 33 +++++++++++++++++++ ...a => PsqlEntityDatabaseSchemaService.java} | 6 ++-- .../TimescaleTsDatabaseUpgradeService.java | 2 +- .../TimescaleInsertTsRepository.java | 2 +- 4 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java rename application/src/main/java/org/thingsboard/server/service/install/{SqlEntityDatabaseSchemaService.java => PsqlEntityDatabaseSchemaService.java} (83%) diff --git a/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java new file mode 100644 index 0000000000..0b3232903b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2020 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.install; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@Service +@HsqlDao +@SqlDao +@Profile("install") +public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService + implements EntityDatabaseSchemaService { + protected HsqlEntityDatabaseSchemaService() { + super("schema-entities-hsql.sql", "schema-entities-idx.sql"); + } +} + diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java similarity index 83% rename from application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java rename to application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java index f124b78842..11da8b306a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlEntityDatabaseSchemaService.java @@ -17,14 +17,16 @@ package org.thingsboard.server.service.install; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlDao; @Service @SqlDao +@PsqlDao @Profile("install") -public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService +public class PsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements EntityDatabaseSchemaService { - public SqlEntityDatabaseSchemaService() { + public PsqlEntityDatabaseSchemaService() { super("schema-entities.sql", "schema-entities-idx.sql"); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index 0c57e2e9bd..a2a9611581 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -104,7 +104,7 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); - executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); + executeQuery(conn, "ALTER TABLE tenant_ts_kv ADD COLUMN json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); log.info("schema timeseries updated!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java index 4cd0a4ab59..738ae52a9d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java @@ -53,7 +53,7 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem if (tsKvEntity.getBooleanValue() != null) { ps.setBoolean(5, tsKvEntity.getBooleanValue()); - ps.setBoolean(9, tsKvEntity.getBooleanValue()); + ps.setBoolean(10, tsKvEntity.getBooleanValue()); } else { ps.setNull(5, Types.BOOLEAN); ps.setNull(10, Types.BOOLEAN); From 118c81da5f3b3d2703268e147fd05308a44eaca1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 28 Feb 2020 22:23:33 +0200 Subject: [PATCH 238/261] remove min and max json query from AggregationRepository --- .../server/dao/sqlts/timescale/AggregationRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index bb0b13f2a6..ed784b96ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -40,9 +40,9 @@ public class AggregationRepository { public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; - public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, MAX(tskv.json_v) AS jsonValue, 'MAX' AS aggType "; + public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, 'MAX' AS aggType "; - public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, MIN(tskv.json_v) AS jsonValue,'MIN' AS aggType "; + public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, 'MIN' AS aggType "; public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, null AS jsonValue, 'SUM' AS aggType "; From db65eac63806591b54bd93816d6126e69f53f1aa Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Thu, 27 Feb 2020 14:30:14 +0200 Subject: [PATCH 239/261] added better logging to ts upgrade --- .../install/AbstractSqlTsDatabaseUpgradeService.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java index 01bed834b8..fe56ac129c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java @@ -25,6 +25,7 @@ import java.nio.file.Path; import java.sql.CallableStatement; import java.sql.Connection; import java.sql.SQLException; +import java.sql.SQLWarning; import java.sql.Types; @Slf4j @@ -93,6 +94,15 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { try { CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); callableStatement.execute(); + SQLWarning warnings = callableStatement.getWarnings(); + if (warnings != null) { + log.info("{}", warnings.getMessage()); + SQLWarning nextWarning = warnings.getNextWarning(); + while (nextWarning != null) { + log.info("{}", nextWarning.getMessage()); + nextWarning = nextWarning.getNextWarning(); + } + } callableStatement.close(); log.info(SUCCESSFULLY_EXECUTED_FUNCTION, query.replace(CALL_REGEX, "")); Thread.sleep(2000); From 0ed0725643758e3161172565f793125c2790b369 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Mon, 2 Mar 2020 17:50:37 +0200 Subject: [PATCH 240/261] [2.5] Feature add support json[ui] (#2446) * Merge with master * Refactoring code * Add license header Co-authored-by: Andrew Shvayka --- ui/src/app/api/widget.service.js | 4 +- ui/src/app/common/types.constant.js | 5 + .../app/components/json-content.directive.js | 32 ++-- ui/src/app/components/json-content.scss | 2 +- ui/src/app/components/json-content.tpl.html | 8 +- .../components/json-object-edit.directive.js | 10 +- ui/src/app/components/json-object-edit.scss | 13 ++ .../app/components/json-object-edit.tpl.html | 8 +- .../components/tb-json-to-string.directive.js | 45 ++++++ .../add-attribute-dialog.controller.js | 38 ++++- .../attribute/add-attribute-dialog.tpl.html | 34 ++++- ...d-widget-to-dashboard-dialog.controller.js | 1 - .../add-widget-to-dashboard-dialog.tpl.html | 20 +-- .../attribute-dialog-edit-json.controller.js | 35 +++++ .../attribute/attribute-dialog-edit-json.scss | 24 +++ .../attribute-dialog-edit-json.tpl.html | 54 +++++++ .../attribute/attribute-table.directive.js | 143 ++++++++++-------- .../app/entity/attribute/attribute-table.scss | 8 + .../entity/attribute/attribute-table.tpl.html | 4 +- .../edit-attribute-value.controller.js | 58 +++++-- .../attribute/edit-attribute-value.tpl.html | 30 +++- ui/src/app/locale/locale.constant-el_GR.json | 2 +- ui/src/app/locale/locale.constant-en_US.json | 8 +- ui/src/app/locale/locale.constant-ru_RU.json | 4 +- ui/src/app/locale/locale.constant-uk_UA.json | 4 +- 25 files changed, 464 insertions(+), 130 deletions(-) create mode 100644 ui/src/app/components/tb-json-to-string.directive.js create mode 100644 ui/src/app/entity/attribute/attribute-dialog-edit-json.controller.js create mode 100644 ui/src/app/entity/attribute/attribute-dialog-edit-json.scss create mode 100644 ui/src/app/entity/attribute/attribute-dialog-edit-json.tpl.html diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index 206d3c3d14..4b706e827d 100644 --- a/ui/src/app/api/widget.service.js +++ b/ui/src/app/api/widget.service.js @@ -29,6 +29,8 @@ import thingsboardWebCameraInputWidget from '../widget/lib/web-camera-input-widg import thingsboardRpcWidgets from '../widget/lib/rpc'; +import thingsboardJsonToString from '../components/tb-json-to-string.directive'; + import TbFlot from '../widget/lib/flot-widget'; import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; import TbAnalogueRadialGauge from '../widget/lib/analogue-radial-gauge'; @@ -52,7 +54,7 @@ export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsbo thingsboardTimeseriesTableWidget, thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardDateRangeNavigatorWidget, thingsboardMultipleInputWidget, thingsboardWebCameraInputWidget, thingsboardRpcWidgets, thingsboardTypes, - thingsboardUtils, TripAnimationWidget]) + thingsboardUtils, thingsboardJsonToString, TripAnimationWidget]) .factory('widgetService', WidgetService) .name; diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 57ac8825f5..86bf952582 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -881,6 +881,11 @@ export default angular.module('thingsboard.types', []) value: "boolean", name: "value.boolean", icon: "mdi:checkbox-marked-outline" + }, + json: { + value: "json", + name: "value.json", + icon: "mdi:json" } }, widgetType: { diff --git a/ui/src/app/components/json-content.directive.js b/ui/src/app/components/json-content.directive.js index 0788216db9..c4281b15c2 100644 --- a/ui/src/app/components/json-content.directive.js +++ b/ui/src/app/components/json-content.directive.js @@ -57,9 +57,12 @@ function JsonContent($compile, $templateCache, toast, types, utils) { updateEditorSize(); }; - scope.beautifyJson = function () { - var res = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60}); - scope.contentBody = res; + scope.beautifyJSON = function () { + scope.contentBody = js_beautify(scope.contentBody, {indent_size: 4, wrap_line_length: 60}); + }; + + scope.minifyJSON = function () { + scope.contentBody = angular.toJson(angular.fromJson(scope.contentBody)); }; function updateEditorSize() { @@ -116,7 +119,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { scope.$watch('contentBody', function (newContent, oldContent) { ngModelCtrl.$setViewValue(scope.contentBody); if (!angular.equals(newContent, oldContent)) { - scope.contentValid = true; + scope.contentValid = scope.validate(); } scope.updateValidity(); }); @@ -139,15 +142,17 @@ function JsonContent($compile, $templateCache, toast, types, utils) { } return true; } catch (e) { - var details = utils.parseException(e); - var errorInfo = 'Error:'; - if (details.name) { - errorInfo += ' ' + details.name + ':'; - } - if (details.message) { - errorInfo += ' ' + details.message; + if (!scope.hideErrorToast) { + var details = utils.parseException(e); + var errorInfo = 'Error:'; + if (details.name) { + errorInfo += ' ' + details.name + ':'; + } + if (details.message) { + errorInfo += ' ' + details.message; + } + scope.showError(errorInfo); } - scope.showError(errorInfo); return false; } }; @@ -169,7 +174,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { }); $compile(element.contents())(scope); - } + }; return { restrict: "E", @@ -177,6 +182,7 @@ function JsonContent($compile, $templateCache, toast, types, utils) { scope: { contentType: '=', validateContent: '=?', + hideErrorToast: '=?', readonly:'=ngReadonly', fillHeight:'=?' }, diff --git a/ui/src/app/components/json-content.scss b/ui/src/app/components/json-content.scss index 86937c3af4..d444f8051f 100644 --- a/ui/src/app/components/json-content.scss +++ b/ui/src/app/components/json-content.scss @@ -27,7 +27,7 @@ tb-json-content { min-height: 15px; padding: 4px; margin: 0 5px 0 0; - font-size: .8rem; + font-size: 12px; line-height: 15px; color: #7b7b7b; background: rgba(220, 220, 220, .35); diff --git a/ui/src/app/components/json-content.tpl.html b/ui/src/app/components/json-content.tpl.html index 5b847f1af6..2b7670942d 100644 --- a/ui/src/app/components/json-content.tpl.html +++ b/ui/src/app/components/json-content.tpl.html @@ -17,11 +17,15 @@ -->
    - + - {{ + {{ 'js-func.tidy' | translate }} + {{ + 'js-func.mini' | translate }} +
    diff --git a/ui/src/app/components/json-object-edit.directive.js b/ui/src/app/components/json-object-edit.directive.js index 61b54faab7..538b1cfc41 100644 --- a/ui/src/app/components/json-object-edit.directive.js +++ b/ui/src/app/components/json-object-edit.directive.js @@ -50,6 +50,14 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { updateEditorSize(); }; + scope.beautifyJSON = function () { + scope.contentBody = angular.toJson(scope.object, 4); + }; + + scope.minifyJSON = function () { + scope.contentBody = angular.toJson(scope.object); + }; + function updateEditorSize() { if (scope.json_editor) { scope.json_editor.resize(); @@ -169,7 +177,7 @@ function JsonObjectEdit($compile, $templateCache, $document, toast, utils) { }); $compile(element.contents())(scope); - } + }; return { restrict: "E", diff --git a/ui/src/app/components/json-object-edit.scss b/ui/src/app/components/json-object-edit.scss index d58d9f4b23..9c9cdc7e44 100644 --- a/ui/src/app/components/json-object-edit.scss +++ b/ui/src/app/components/json-object-edit.scss @@ -21,6 +21,19 @@ tb-json-object-edit { } } +.tb-json-object-edit-toolbar { + .md-button.tidy { + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0 5px 0 0; + font-size: 12px; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + } +} + .tb-json-object-panel { height: 100%; margin-left: 15px; diff --git a/ui/src/app/components/json-object-edit.tpl.html b/ui/src/app/components/json-object-edit.tpl.html index aa6c867555..86f2a55650 100644 --- a/ui/src/app/components/json-object-edit.tpl.html +++ b/ui/src/app/components/json-object-edit.tpl.html @@ -16,12 +16,18 @@ -->
    -
    +
    + + {{'js-func.tidy' | translate }} + + + {{'js-func.mini' | translate }} +
    diff --git a/ui/src/app/components/tb-json-to-string.directive.js b/ui/src/app/components/tb-json-to-string.directive.js new file mode 100644 index 0000000000..79bfadb540 --- /dev/null +++ b/ui/src/app/components/tb-json-to-string.directive.js @@ -0,0 +1,45 @@ +/* + * Copyright © 2016-2020 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. + */ +export default angular.module('tbJsonToString', []) + .directive('tbJsonToString', InputJson) + .name; + +function InputJson() { + return { + restrict: 'A', + require: 'ngModel', + link: function(scope, element, attr, ngModelCtrl) { + function into(input) { + try { + ngModelCtrl.$setValidity('invalidJSON', true); + return angular.fromJson(input); + } catch (e) { + ngModelCtrl.$setValidity('invalidJSON', false); + } + } + function out(data) { + try { + ngModelCtrl.$setValidity('invalidJSON', true); + return angular.toJson(data); + } catch (e) { + ngModelCtrl.$setValidity('invalidJSON', false); + } + } + ngModelCtrl.$parsers.push(into); + ngModelCtrl.$formatters.push(out); + } + }; +} diff --git a/ui/src/app/entity/attribute/add-attribute-dialog.controller.js b/ui/src/app/entity/attribute/add-attribute-dialog.controller.js index 6b277f08ef..e1c711ba66 100644 --- a/ui/src/app/entity/attribute/add-attribute-dialog.controller.js +++ b/ui/src/app/entity/attribute/add-attribute-dialog.controller.js @@ -13,10 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* eslint-disable import/no-unresolved, import/default */ + +import attributeDialogEditJsonTemplate from './attribute-dialog-edit-json.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +import AttributeDialogEditJsonController from './attribute-dialog-edit-json.controller'; + /*@ngInject*/ export default function AddAttributeDialogController($scope, $mdDialog, types, attributeService, entityType, entityId, attributeScope) { - var vm = this; + let vm = this; vm.attribute = {}; @@ -40,11 +48,37 @@ export default function AddAttributeDialogController($scope, $mdDialog, types, a ); } - $scope.$watch('vm.valueType', function() { + $scope.$watch('vm.valueType', function () { if (vm.valueType === types.valueType.boolean) { vm.attribute.value = false; + } else if (vm.valueType === types.valueType.json) { + vm.attribute.value = {}; } else { vm.attribute.value = null; } }); + + vm.addJSON = ($event) => { + showJsonDialog($event, vm.attribute.value, false).then((response) => { + vm.attribute.value = response; + }) + }; + + function showJsonDialog($event, jsonValue, readOnly) { + if ($event) { + $event.stopPropagation(); + } + return $mdDialog.show({ + controller: AttributeDialogEditJsonController, + controllerAs: 'vm', + templateUrl: attributeDialogEditJsonTemplate, + locals: { + jsonValue: jsonValue, + readOnly: readOnly + }, + targetEvent: $event, + fullscreen: true, + multiple: true, + }); + } } diff --git a/ui/src/app/entity/attribute/add-attribute-dialog.tpl.html b/ui/src/app/entity/attribute/add-attribute-dialog.tpl.html index f13e31eee9..6520d7a541 100644 --- a/ui/src/app/entity/attribute/add-attribute-dialog.tpl.html +++ b/ui/src/app/entity/attribute/add-attribute-dialog.tpl.html @@ -26,7 +26,8 @@
    - +
    @@ -58,7 +59,8 @@ - +
    attribute.value-required
    value.invalid-integer-value
    @@ -71,11 +73,31 @@
    attribute.value-required
    -
    - +
    + {{ (vm.attribute.value ? 'value.true' : 'value.false') | translate }}
    +
    + + + +
    +
    attribute.value-required
    +
    +
    + + + {{ 'action.edit' | translate }} + + + +
    @@ -87,8 +109,8 @@ class="md-raised md-primary"> {{ 'action.add' | translate }} - {{ 'action.cancel' | - translate }} + + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js index 173132a4b3..076bee89d5 100644 --- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js +++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js @@ -151,5 +151,4 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog, } ); } - } diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html index 10d7e4ce55..b6c1746b41 100644 --- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html +++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.tpl.html @@ -26,7 +26,8 @@
    - +
    @@ -37,10 +38,10 @@
    dashboard.select-existing + ng-disabled="$root.loading || vm.addToDashboardType != 0" + tb-required="vm.addToDashboardType === 0" + ng-model="vm.dashboardId" + select-first-dashboard="false">
    @@ -49,7 +50,8 @@ dashboard.create-new - +
    dashboard.title-required
    @@ -66,15 +68,15 @@ + style="margin-bottom: 0; padding-right: 20px;"> {{ 'dashboard.open-dashboard' | translate }} {{ 'action.add' | translate }} - {{ 'action.cancel' | - translate }} + + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/entity/attribute/attribute-dialog-edit-json.controller.js b/ui/src/app/entity/attribute/attribute-dialog-edit-json.controller.js new file mode 100644 index 0000000000..e53d168a68 --- /dev/null +++ b/ui/src/app/entity/attribute/attribute-dialog-edit-json.controller.js @@ -0,0 +1,35 @@ +/* + * Copyright © 2016-2020 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. + */ +/* eslint-enable import/no-unresolved, import/default */ + +import './attribute-dialog-edit-json.scss'; + +/*@ngInject*/ +export default function AttributeDialogEditJsonController($mdDialog, types, jsonValue, readOnly) { + + let vm = this; + vm.json = angular.toJson(jsonValue, 4); + vm.readOnly = readOnly; + vm.contentType = types.contentType.JSON.value; + + vm.save = () => { + $mdDialog.hide(angular.fromJson(vm.json)); + }; + + vm.cancel = () => { + $mdDialog.cancel(); + }; +} diff --git a/ui/src/app/entity/attribute/attribute-dialog-edit-json.scss b/ui/src/app/entity/attribute/attribute-dialog-edit-json.scss new file mode 100644 index 0000000000..84219ac11a --- /dev/null +++ b/ui/src/app/entity/attribute/attribute-dialog-edit-json.scss @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 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. + */ +.attribute-edit-json-dialog{ + min-width: 400px; +} + +@media (max-width: 425px){ + .attribute-edit-json-dialog { + min-width: 200px; + } +} diff --git a/ui/src/app/entity/attribute/attribute-dialog-edit-json.tpl.html b/ui/src/app/entity/attribute/attribute-dialog-edit-json.tpl.html new file mode 100644 index 0000000000..1c801edc10 --- /dev/null +++ b/ui/src/app/entity/attribute/attribute-dialog-edit-json.tpl.html @@ -0,0 +1,54 @@ + + +
    + +
    +

    {{ 'details.edit-json' | translate }}

    + + + + +
    +
    + +
    + + +
    +
    + + + {{'action.save'|translate}} + + + {{'action.cancel'|translate }} + + + +
    diff --git a/ui/src/app/entity/attribute/attribute-table.directive.js b/ui/src/app/entity/attribute/attribute-table.directive.js index 3ba1f51744..59ea4fe91d 100644 --- a/ui/src/app/entity/attribute/attribute-table.directive.js +++ b/ui/src/app/entity/attribute/attribute-table.directive.js @@ -39,7 +39,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS element.html(template); - var getAttributeScopeByValue = function(attributeScopeValue) { + var getAttributeScopeByValue = function (attributeScopeValue) { if (scope.types.latestTelemetry.value === attributeScopeValue) { return scope.types.latestTelemetry; } @@ -48,7 +48,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS return scope.attributeScopes[attrScope]; } } - } + }; scope.types = types; @@ -87,14 +87,14 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS search: null }; - scope.$watch("entityId", function(newVal) { + scope.$watch("entityId", function (newVal) { if (newVal) { scope.resetFilter(); scope.getEntityAttributes(false, true); } }); - scope.$watch("attributeScope", function(newVal, prevVal) { + scope.$watch("attributeScope", function (newVal, prevVal) { if (newVal && !angular.equals(newVal, prevVal)) { scope.mode = 'default'; scope.query.search = null; @@ -103,30 +103,30 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } }); - scope.resetFilter = function() { + scope.resetFilter = function () { scope.mode = 'default'; scope.query.search = null; scope.selectedAttributes = []; scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope); - } + }; - scope.enterFilterMode = function(event) { + scope.enterFilterMode = function (event) { let $button = angular.element(event.currentTarget); let $toolbarsContainer = $button.closest('.toolbarsContainer'); scope.query.search = ''; - $timeout(()=>{ + $timeout(() => { $toolbarsContainer.find('.searchInput').focus(); }) - } + }; - scope.exitFilterMode = function() { + scope.exitFilterMode = function () { scope.query.search = null; scope.getEntityAttributes(); - } + }; - scope.$watch("query.search", function(newVal, prevVal) { + scope.$watch("query.search", function (newVal, prevVal) { if (!angular.equals(newVal, prevVal) && scope.query.search != null) { scope.getEntityAttributes(); } @@ -142,15 +142,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } } - scope.onReorder = function() { + scope.onReorder = function () { scope.getEntityAttributes(false, false); - } + }; - scope.onPaginate = function() { + scope.onPaginate = function () { scope.getEntityAttributes(false, false); - } + }; - scope.getEntityAttributes = function(forceUpdate, reset) { + scope.getEntityAttributes = function (forceUpdate, reset) { if (scope.attributesDeferred) { scope.attributesDeferred.resolve(); } @@ -163,7 +163,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } scope.checkSubscription(); scope.attributesDeferred = attributeService.getEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, - scope.query, function(attributes, update, apply) { + scope.query, function (attributes, update, apply) { success(attributes, update || forceUpdate, apply); } ); @@ -176,9 +176,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS }); deferred.resolve(); } - } + }; - scope.checkSubscription = function() { + scope.checkSubscription = function () { var newSubscriptionId = null; if (scope.entityId && scope.entityType && scope.attributeScope.clientSide && scope.mode != 'widget') { newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value); @@ -187,36 +187,38 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); } scope.subscriptionId = newSubscriptionId; - } + }; - scope.$on('$destroy', function() { + scope.$on('$destroy', function () { if (scope.subscriptionId) { attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); } }); - scope.editAttribute = function($event, attribute) { + scope.editAttribute = function ($event, attribute) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); $mdEditDialog.show({ controller: EditAttributeValueController, templateUrl: editAttributeValueTemplate, - locals: {attributeValue: attribute.value, - save: function (model) { - var updatedAttribute = angular.copy(attribute); - updatedAttribute.value = model.value; - attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then( - function success() { - scope.getEntityAttributes(); - } - ); - }}, + locals: { + attributeValue: attribute.value, + save: function (model) { + var updatedAttribute = angular.copy(attribute); + updatedAttribute.value = model.value; + attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then( + function success() { + scope.getEntityAttributes(); + } + ); + } + }, targetEvent: $event }); } - } + }; - scope.addAttribute = function($event) { + scope.addAttribute = function ($event) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); $mdDialog.show({ @@ -224,16 +226,20 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS controllerAs: 'vm', templateUrl: addAttributeDialogTemplate, parent: angular.element($document[0].body), - locals: {entityType: scope.entityType, entityId: scope.entityId, attributeScope: scope.attributeScope.value}, + locals: { + entityType: scope.entityType, + entityId: scope.entityId, + attributeScope: scope.attributeScope.value + }, fullscreen: true, targetEvent: $event }).then(function () { scope.getEntityAttributes(); }); } - } + }; - scope.deleteAttributes = function($event) { + scope.deleteAttributes = function ($event) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); var confirm = $mdDialog.confirm() @@ -244,33 +250,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS .cancel($translate.instant('action.no')) .ok($translate.instant('action.yes')); $mdDialog.show(confirm).then(function () { - attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then( - function success() { - scope.selectedAttributes = []; - scope.getEntityAttributes(); - } - ) + attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then( + function success() { + scope.selectedAttributes = []; + scope.getEntityAttributes(); + } + ) }); } - } + }; - scope.nextWidget = function() { + scope.nextWidget = function () { $mdUtil.nextTick(function () { if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) { scope.widgetsCarousel.index++; } }); - } + }; - scope.prevWidget = function() { + scope.prevWidget = function () { $mdUtil.nextTick(function () { if (scope.widgetsCarousel.index > 0) { scope.widgetsCarousel.index--; } }); - } + }; - scope.enterWidgetMode = function() { + scope.enterWidgetMode = function () { if (scope.widgetsIndexWatch) { scope.widgetsIndexWatch(); @@ -303,7 +309,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS entitiAliases[entityAlias.id] = entityAlias; var stateController = { - getStateParams: function() { + getStateParams: function () { return {}; } }; @@ -317,9 +323,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS type: types.datasourceType.entity, entityAliasId: entityAlias.id, dataKeys: [] - } + }; var i = 0; - for (var attr =0; attr < scope.selectedAttributes.length;attr++) { + for (var attr = 0; attr < scope.selectedAttributes.length; attr++) { var attribute = scope.selectedAttributes[attr]; var dataKey = { name: attribute.key, @@ -328,12 +334,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS color: utils.getMaterialColor(i), settings: {}, _hash: Math.random() - } + }; datasource.dataKeys.push(dataKey); i++; } - scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) { + scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function (newVal, prevVal) { if (scope.mode === 'widget' && (newVal != prevVal)) { var index = scope.widgetsCarousel.index; for (var i = 0; i < scope.widgetsList.length; i++) { @@ -345,7 +351,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } }); - scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) { + scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function (newVal, prevVal) { if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) { scope.widgetsList = []; scope.widgetsListCache = []; @@ -358,7 +364,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then( function success(widgetTypes) { - widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']); + widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type', '-createdTime']); for (var i = 0; i < widgetTypes.length; i++) { var widgetType = widgetTypes[i]; @@ -398,9 +404,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } } }); - } + }; - scope.exitWidgetMode = function() { + scope.exitWidgetMode = function () { if (scope.widgetsBundleWatch) { scope.widgetsBundleWatch(); scope.widgetsBundleWatch = null; @@ -412,9 +418,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS scope.selectedWidgetsBundleAlias = null; scope.mode = 'default'; scope.getEntityAttributes(true); - } + }; - scope.addWidgetToDashboard = function($event) { + scope.addWidgetToDashboard = function ($event) { if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) { var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0]; $event.stopPropagation(); @@ -423,21 +429,26 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS controllerAs: 'vm', templateUrl: addWidgetToDashboardDialogTemplate, parent: angular.element($document[0].body), - locals: {entityId: scope.entityId, entityType: scope.entityType, entityName: scope.entityName, widget: angular.copy(widget)}, + locals: { + entityId: scope.entityId, + entityType: scope.entityType, + entityName: scope.entityName, + widget: angular.copy(widget) + }, fullscreen: true, targetEvent: $event }).then(function () { }); } - } + }; - scope.loading = function() { + scope.loading = function () { return $rootScope.loading; - } + }; $compile(element.contents())(scope); - } + }; return { restrict: "E", diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss index dd48a5d4b5..5a0bde0c68 100644 --- a/ui/src/app/entity/attribute/attribute-table.scss +++ b/ui/src/app/entity/attribute/attribute-table.scss @@ -58,3 +58,11 @@ md-toolbar.md-table-toolbar.alternate { } } } + +md-edit-dialog.tb-edit-dialog{ + z-index: 78; +} + +md-backdrop.md-edit-dialog-backdrop{ + z-index: 77; +} diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html index e6a8b12d3d..8844891e62 100644 --- a/ui/src/app/entity/attribute/attribute-table.tpl.html +++ b/ui/src/app/entity/attribute/attribute-table.tpl.html @@ -77,9 +77,7 @@
    - diff --git a/ui/src/app/entity/attribute/edit-attribute-value.controller.js b/ui/src/app/entity/attribute/edit-attribute-value.controller.js index 208ad9a678..7cb550d4c1 100644 --- a/ui/src/app/entity/attribute/edit-attribute-value.controller.js +++ b/ui/src/app/entity/attribute/edit-attribute-value.controller.js @@ -13,14 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +/* eslint-enable import/no-unresolved, import/default */ + +import AttributeDialogEditJsonController from "./attribute-dialog-edit-json.controller"; +import attributeDialogEditJsonTemplate from "./attribute-dialog-edit-json.tpl.html"; + /*@ngInject*/ -export default function EditAttributeValueController($scope, $q, $element, types, attributeValue, save) { +export default function EditAttributeValueController($scope, $mdDialog, $q, $element, $document, types, attributeValue, save) { $scope.valueTypes = types.valueType; - $scope.model = {}; - - $scope.model.value = attributeValue; + $scope.model = { + value: attributeValue + }; if ($scope.model.value === true || $scope.model.value === false) { $scope.valueType = types.valueType.boolean; @@ -30,26 +35,27 @@ export default function EditAttributeValueController($scope, $q, $element, types } else { $scope.valueType = types.valueType.double; } + } else if (angular.isObject($scope.model.value)) { + $scope.valueType = types.valueType.json; } else { $scope.valueType = types.valueType.string; } $scope.submit = submit; $scope.dismiss = dismiss; + $scope.editJSON = editJSON; function dismiss() { $element.remove(); } function update() { - if($scope.editDialog.$invalid) { + if ($scope.editDialog.$invalid) { return $q.reject(); } - - if(angular.isFunction(save)) { + if (angular.isFunction(save)) { return $q.when(save($scope.model)); } - return $q.resolve(); } @@ -59,13 +65,45 @@ export default function EditAttributeValueController($scope, $q, $element, types }); } - $scope.$watch('valueType', function(newVal, prevVal) { - if (newVal != prevVal) { + + $scope.$watch('valueType', function (newVal, prevVal) { + if (newVal !== prevVal) { if ($scope.valueType === types.valueType.boolean) { $scope.model.value = false; + } else if ($scope.valueType === types.valueType.json) { + $scope.model.value = {}; } else { $scope.model.value = null; } } }); + + function editJSON($event) { + $scope.hideDialog = true; + showJsonDialog($event, $scope.model.value, false).then((response) => { + $scope.hideDialog = false; + if (!angular.equals(response, $scope.model.value)) { + $scope.editDialog.$setDirty(); + } + $scope.model.value = response; + }) + } + + function showJsonDialog($event, jsonValue, readOnly) { + if ($event) { + $event.stopPropagation(); + } + return $mdDialog.show({ + controller: AttributeDialogEditJsonController, + controllerAs: 'vm', + templateUrl: attributeDialogEditJsonTemplate, + locals: { + jsonValue: jsonValue, + readOnly: readOnly + }, + targetEvent: $event, + fullscreen: true, + multiple: true + }); + } } diff --git a/ui/src/app/entity/attribute/edit-attribute-value.tpl.html b/ui/src/app/entity/attribute/edit-attribute-value.tpl.html index ca4f8181f1..35abe746aa 100644 --- a/ui/src/app/entity/attribute/edit-attribute-value.tpl.html +++ b/ui/src/app/entity/attribute/edit-attribute-value.tpl.html @@ -15,8 +15,8 @@ limitations under the License. --> - -
    + +
    @@ -38,7 +38,8 @@ - +
    attribute.value-required
    value.invalid-integer-value
    @@ -52,16 +53,31 @@
    - + {{ (model.value ? 'value.true' : 'value.false') | translate }}
    +
    + + + +
    +
    attribute.value-required
    +
    +
    + + + {{ 'action.edit' | translate }} + + + +
    - {{ 'action.cancel' | - translate }} + {{ 'action.cancel' | translate }} @@ -69,4 +85,4 @@
    -
    \ No newline at end of file +
    diff --git a/ui/src/app/locale/locale.constant-el_GR.json b/ui/src/app/locale/locale.constant-el_GR.json index 1d81f7ea19..8d4951f28f 100644 --- a/ui/src/app/locale/locale.constant-el_GR.json +++ b/ui/src/app/locale/locale.constant-el_GR.json @@ -2232,7 +2232,7 @@ "last": "Τελευταίος", "time-period": "Χρονική Περίοδος" }, - "user": { + "user": { "user": "Χρήστης", "users": "Χρήστες", "management": "Διαχείριση Χρηστών", diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index d9e33e4488..f0dc673f84 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -607,6 +607,7 @@ }, "details": { "edit-mode": "Edit mode", + "edit-json": "Edit JSON", "toggle-edit-mode": "Toggle edit mode" }, "device": { @@ -1270,7 +1271,8 @@ "js-func": { "no-return-error": "Function must return value!", "return-type-mismatch": "Function must return value of '{{type}}' type!", - "tidy": "Tidy" + "tidy": "Tidy", + "mini": "Mini" }, "key-val": { "key": "Key", @@ -1593,7 +1595,9 @@ "boolean-value": "Boolean value", "false": "False", "true": "True", - "long": "Long" + "long": "Long", + "json": "JSON", + "json-value": "JSON value" }, "widget": { "widget-library": "Widgets Library", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index d5284ebd1a..47c70da48a 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -607,6 +607,7 @@ }, "details": { "edit-mode": "Режим редактирования", + "edit-json": "Редактировать JSON", "toggle-edit-mode": "Режим редактирования" }, "device": { @@ -1191,8 +1192,7 @@ }, "js-func": { "no-return-error": "Функция должна возвращать значение!", - "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!", - "tidy": "Tidy" + "return-type-mismatch": "Функция должна возвращать значение типа '{{type}}'!" }, "key-val": { "key": "Ключ", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index 75124022dd..7b882423ed 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -724,6 +724,7 @@ "details": { "details": "Деталі", "edit-mode": "Режим редагування", + "edit-json": "Редагувати JSON", "toggle-edit-mode": "Перемкнути режим редагування" }, "device": { @@ -1606,8 +1607,7 @@ }, "js-func": { "no-return-error": "Функція повинна повертати значення!", - "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!", - "tidy": "Tidy" + "return-type-mismatch": "Функція повинна повернути значення типу '{{type}}'!" }, "key-val": { "key": "Ключ", From a58db6b7ecaa36d1d4b86be5e79ec1c736486f91 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 3 Mar 2020 11:30:41 +0200 Subject: [PATCH 241/261] added query delete to JavaAttributeDao --- .../dao/sql/attributes/AttributeKvRepository.java | 13 +++++++++++++ .../server/dao/sql/attributes/JpaAttributeDao.java | 12 +++--------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index 226fa6df8e..0bd667b790 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.dao.sql.attributes; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; @@ -34,5 +36,16 @@ public interface AttributeKvRepository extends CrudRepository findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType, @Param("entityId") String entityId, @Param("attributeType") String attributeType); + + @Transactional + @Modifying + @Query("DELETE FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + + "AND a.id.entityId = :entityId " + + "AND a.id.attributeType = :attributeType " + + "AND a.id.attributeKey = :attributeKey") + void delete(@Param("entityType") EntityType entityType, + @Param("entityId") String entityId, + @Param("attributeType") String attributeType, + @Param("attributeKey") String attributeKey); } 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 ab9964d3e3..c14e2dd7d0 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 @@ -138,16 +138,10 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Override public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List keys) { - List entitiesToDelete = keys - .stream() - .map(key -> { - AttributeKvEntity entityToDelete = new AttributeKvEntity(); - entityToDelete.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, key)); - return entityToDelete; - }).collect(Collectors.toList()); - return service.submit(() -> { - attributeKvRepository.deleteAll(entitiesToDelete); + keys.forEach(key -> + attributeKvRepository.delete(entityId.getEntityType(), UUIDConverter.fromTimeUUID(entityId.getId()), attributeType, key) + ); return null; }); } From 90ae060cbeb3a7095924c136322d7edfb88ded4a Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 4 Mar 2020 10:09:59 +0200 Subject: [PATCH 242/261] Add gauge color limit (#2479) --- ui/src/app/widget/lib/CanvasDigitalGauge.js | 41 ++- ui/src/app/widget/lib/canvas-digital-gauge.js | 251 +++++++++++++++++- 2 files changed, 275 insertions(+), 17 deletions(-) diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index 424fad3901..3e914064a1 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -104,26 +104,32 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { } var colorsCount = options.levelColors.length; - var inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; + const inc = colorsCount > 1 ? (1 / (colorsCount - 1)) : 1; + var isColorProperty = angular.isString(options.levelColors[0]); + options.colorsRange = []; if (options.neonGlowBrightness) { options.neonColorsRange = []; } - for (var i = 0; i < options.levelColors.length; i++) { - var percentage = inc * i; - var tColor = tinycolor(options.levelColors[i]); - options.colorsRange[i] = { - pct: percentage, - color: tColor.toRgb(), - rgbString: tColor.toRgbString() - }; - if (options.neonGlowBrightness) { - tColor = tinycolor(options.levelColors[i]).brighten(options.neonGlowBrightness); - options.neonColorsRange[i] = { + + for (let i = 0; i < options.levelColors.length; i++) { + const levelColor = options.levelColors[i]; + if (levelColor !== null) { + let percentage = isColorProperty ? inc * i : CanvasDigitalGauge.normalizeValue(levelColor.value, options.minValue, options.maxValue); + let tColor = tinycolor(isColorProperty ? levelColor : levelColor.color); + options.colorsRange[i] = { pct: percentage, color: tColor.toRgb(), rgbString: tColor.toRgbString() }; + if (options.neonGlowBrightness) { + tColor = tinycolor(isColorProperty ? levelColor : levelColor.color).brighten(options.neonGlowBrightness); + options.neonColorsRange[i] = { + pct: percentage, + color: tColor.toRgb(), + rgbString: tColor.toRgbString() + }; + } } } @@ -137,6 +143,17 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { return canvasGauges.BaseGauge.configure(options); } + static normalizeValue (value, min, max) { + let normalValue = (value - min) / (max - min); + if (normalValue <= 0) { + return 0; + } + if (normalValue >= 1) { + return 1; + } + return normalValue; + } + destroy() { this.contextValueClone = null; this.elementValueClone = null; diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index 71f40d77fa..c0eed09c20 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -50,10 +50,16 @@ export default class TbCanvasDigitalGauge { this.localSettings.gaugeWidthScale = settings.gaugeWidthScale || 0.75; this.localSettings.gaugeColor = settings.gaugeColor || tinycolor(keyColor).setAlpha(0.2).toRgbString(); - if (!settings.levelColors || settings.levelColors.length <= 0) { - this.localSettings.levelColors = [keyColor]; + this.localSettings.useFixedLevelColor = settings.useFixedLevelColor || false; + if (!settings.useFixedLevelColor) { + if (!settings.levelColors || settings.levelColors.length <= 0) { + this.localSettings.levelColors = [keyColor]; + } else { + this.localSettings.levelColors = settings.levelColors.slice(); + } } else { - this.localSettings.levelColors = settings.levelColors.slice(); + this.localSettings.levelColors = [keyColor]; + this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; } this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : @@ -191,15 +197,137 @@ export default class TbCanvasDigitalGauge { }; this.gauge = new CanvasDigitalGauge(gaugeData).draw(); + this.init(); + } + + init() { + if (this.localSettings.useFixedLevelColor) { + if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { + this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); + this.updateLevelColors(this.localSettings.levelColors); + } + } + } + + settingLevelColorsSubscribe(options) { + let levelColorsDatasource = []; + let predefineLevelColors = []; + + function setLevelColor(levelSetting, color) { + if (levelSetting.valueSource === 'predefinedValue' && isFinite(levelSetting.value)) { + predefineLevelColors.push({ + value: levelSetting.value, + color: color + }) + } else if (levelSetting.entityAlias && levelSetting.attribute) { + let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias); + if (!entityAliasId) { + return; + } + + let datasource = levelColorsDatasource.filter((datasource) => { + return datasource.entityAliasId === entityAliasId; + })[0]; + + let dataKey = { + type: this.ctx.$scope.$injector.get('types').dataKeyType.attribute, + name: levelSetting.attribute, + label: levelSetting.attribute, + settings: [{ + color: color, + index: predefineLevelColors.length + }], + _hash: Math.random() + }; + + if (datasource) { + let findDataKey = datasource.dataKeys.filter((dataKey) => { + return dataKey.name === levelSetting.attribute; + })[0]; + + if (findDataKey) { + findDataKey.settings.push({ + color: color, + index: predefineLevelColors.length + }); + } else { + datasource.dataKeys.push(dataKey) + } + } else { + datasource = { + type: this.ctx.$scope.$injector.get('types').datasourceType.entity, + name: levelSetting.entityAlias, + aliasName: levelSetting.entityAlias, + entityAliasId: entityAliasId, + dataKeys: [dataKey] + }; + levelColorsDatasource.push(datasource); + } + + predefineLevelColors.push(null); + } + } + + for (let i = 0; i < options.length; i++) { + let levelColor = options[i]; + if (levelColor.from) { + setLevelColor.call(this, levelColor.from, levelColor.color); + } + if (levelColor.to) { + setLevelColor.call(this, levelColor.to, levelColor.color); + } + } + this.subscribeLevelColorsAttributes(levelColorsDatasource); + + return predefineLevelColors; + } + + updateLevelColors(levelColors) { + this.gauge.options.levelColors = levelColors; + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); + this.gauge.update(); + } + + subscribeLevelColorsAttributes(datasources) { + let TbCanvasDigitalGauge = this; + let levelColorsSourcesSubscriptionOptions = { + datasources: datasources, + useDashboardTimewindow: false, + type: this.ctx.$scope.$injector.get('types').widgetType.latest.value, + callbacks: { + onDataUpdated: (subscription) => { + for (let i = 0; i < subscription.data.length; i++) { + let keyData = subscription.data[i]; + if (keyData && keyData.data && keyData.data[0]) { + let attrValue = keyData.data[0][1]; + if (isFinite(attrValue)) { + for (let i = 0; i < keyData.dataKey.settings.length; i++) { + let setting = keyData.dataKey.settings[i]; + this.localSettings.levelColors[setting.index] = { + value: attrValue, + color: setting.color + }; + } + } + } + } + this.updateLevelColors(this.localSettings.levelColors); + } + } + }; + this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).then( + (subscription) => { + TbCanvasDigitalGauge.levelColorSourcesSubscription = subscription; + } + ); } update() { if (this.ctx.data.length > 0) { var cellData = this.ctx.data[0]; if (cellData.data.length > 0) { - var tvPair = cellData.data[cellData.data.length - - 1]; + var tvPair = cellData.data[cellData.data.length - 1]; var timestamp; if (this.localSettings.showTimestamp) { timestamp = tvPair[0]; @@ -325,6 +453,11 @@ export default class TbCanvasDigitalGauge { "type": "string", "default": null }, + "useFixedLevelColor": { + "title": "Use precise value for the color indicator", + "type": "boolean", + "default": false + }, "levelColors": { "title": "Colors of indicator, from lower to upper", "type": "array", @@ -333,6 +466,66 @@ export default class TbCanvasDigitalGauge { "type": "string" } }, + "fixedLevelColors": { + "title": "The colors for the indicator using boundary values", + "type": "array", + "items": { + "title": "levelColor", + "type": "object", + "properties": { + "from": { + "title": "From", + "type": "object", + "properties": { + "valueSource": { + "title": "[From] Value source", + "type": "string", + "default": "predefinedValue" + }, + "entityAlias": { + "title": "[From] Source entity alias", + "type": "string" + }, + "attribute": { + "title": "[From] Source entity attribute", + "type": "string" + }, + "value": { + "title": "[From] Value (if predefined value is selected)", + "type": "number" + } + } + }, + "to": { + "title": "To", + "type": "object", + "properties": { + "valueSource": { + "title": "[To] Value source", + "type": "string", + "default": "predefinedValue" + }, + "entityAlias": { + "title": "[To] Source entity alias", + "type": "string" + }, + "attribute": { + "title": "[To] Source entity attribute", + "type": "string" + }, + "value": { + "title": "[To] Value (if predefined value is selected)", + "type": "number" + } + } + }, + "color": { + "title": "Color", + "type": "string" + } + } + } + }, "animation": { "title": "Enable animation", "type": "boolean", @@ -521,8 +714,10 @@ export default class TbCanvasDigitalGauge { "key": "gaugeColor", "type": "color" }, + "useFixedLevelColor", { "key": "levelColors", + "condition": "model.useFixedLevelColor !== true", "items": [ { "key": "levelColors[]", @@ -530,6 +725,52 @@ export default class TbCanvasDigitalGauge { } ] }, + { + "key": "fixedLevelColors", + "condition": "model.useFixedLevelColor === true", + "items": [ + { + "key": "fixedLevelColors[].from.valueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "fixedLevelColors[].from.value", + "fixedLevelColors[].from.entityAlias", + "fixedLevelColors[].from.attribute", + { + "key": "fixedLevelColors[].to.valueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "fixedLevelColors[].to.value", + "fixedLevelColors[].to.entityAlias", + "fixedLevelColors[].to.attribute", + { + "key": "fixedLevelColors[].color", + "type": "color" + } + ] + }, "animation", "animationDuration", { From 244bcc7822ac57c3f1040fd7f6a057fb56e4b13e Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Wed, 4 Mar 2020 15:47:49 +0200 Subject: [PATCH 243/261] fix tls version (#2450) --- ui/src/app/admin/admin.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/admin/admin.controller.js b/ui/src/app/admin/admin.controller.js index fa1c9920ee..8a2f04c621 100644 --- a/ui/src/app/admin/admin.controller.js +++ b/ui/src/app/admin/admin.controller.js @@ -25,7 +25,7 @@ export default function AdminController(adminService, toast, $scope, $rootScope, return protocol; }); - vm.tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; + vm.tlsVersions = ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3']; $translate('admin.test-mail-sent').then(function (translation) { vm.testMailSent = translation; From a9df9df99e9902ce1c20759cd0681c43def5b350 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 4 Mar 2020 15:56:12 +0200 Subject: [PATCH 244/261] Remove unnecessary Admin settings validation --- .../server/controller/BaseAdminControllerTest.java | 14 +------------- .../dao/settings/AdminSettingsServiceImpl.java | 3 --- .../dao/service/BaseAdminSettingsServiceTest.java | 9 --------- 3 files changed, 1 insertion(+), 25 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java index 43b8f6caf1..61fa5bd526 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java @@ -92,19 +92,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("is prohibited"))); } - - @Test - public void testSaveAdminSettingsWithNewJsonStructure() throws Exception { - loginSysAdmin(); - AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); - JsonNode json = adminSettings.getJsonValue(); - ((ObjectNode) json).put("newKey", "my new value"); - adminSettings.setJsonValue(json); - doPost("/api/admin/settings", adminSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Provided json structure is different"))); - } - + @Test public void testSendTestMail() throws Exception { loginSysAdmin(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index a016d02d73..6c7495cc19 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -73,9 +73,6 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) { throw new DataValidationException("Changing key of admin settings entry is prohibited!"); } - if (adminSettings.getKey().equals("mail")) { - validateJsonStructure(existentAdminSettings.getJsonValue(), adminSettings.getJsonValue()); - } } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java index 71aaedef2c..9d5f391dc1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAdminSettingsServiceTest.java @@ -67,13 +67,4 @@ public abstract class BaseAdminSettingsServiceTest extends AbstractServiceTest { adminSettings.setKey("newKey"); adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings); } - - @Test(expected = DataValidationException.class) - public void testSaveAdminSettingsWithNewJsonStructure() { - AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(SYSTEM_TENANT_ID, "mail"); - JsonNode json = adminSettings.getJsonValue(); - ((ObjectNode) json).put("newKey", "my new value"); - adminSettings.setJsonValue(json); - adminSettingsService.saveAdminSettings(SYSTEM_TENANT_ID, adminSettings); - } } From 31b06fefdd17d30307888768ee7bcb29df5cc6da Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Wed, 4 Mar 2020 10:09:55 +0200 Subject: [PATCH 245/261] added InsertRepository for Relation Dao --- .../AbstractRelationInsertRepository.java | 51 +++++++++++++++++++ .../HsqlRelationInsertRepository.java | 47 +++++++++++++++++ .../dao/sql/relation/JpaRelationDao.java | 7 ++- .../PsqlRelationInsertRepository.java | 43 ++++++++++++++++ .../relation/RelationInsertRepository.java | 24 +++++++++ 5 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java new file mode 100644 index 0000000000..1d8fd11e7b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 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.relation; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.jpa.repository.Modifying; +import org.thingsboard.server.dao.model.sql.RelationEntity; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; + +@Slf4j +public abstract class AbstractRelationInsertRepository implements RelationInsertRepository { + + @PersistenceContext + protected EntityManager entityManager; + + protected Query getQuery(RelationEntity entity, String query) { + Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class); + if (entity.getAdditionalInfo().isNull()) { + nativeQuery.setParameter("additionalInfo", null); + } else { + nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().asText()); + } + return nativeQuery + .setParameter("fromId", entity.getFromId()) + .setParameter("fromType", entity.getFromType()) + .setParameter("toId", entity.getToId()) + .setParameter("toType", entity.getToType()) + .setParameter("relationTypeGroup", entity.getRelationTypeGroup()) + .setParameter("relationType", entity.getRelationType()); + } + + @Modifying + protected abstract RelationEntity processSaveOrUpdate(RelationEntity entity); + +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java new file mode 100644 index 0000000000..8438999302 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/HsqlRelationInsertRepository.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 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.relation; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.RelationCompositeKey; +import org.thingsboard.server.dao.model.sql.RelationEntity; +import org.thingsboard.server.dao.util.HsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@HsqlDao +@SqlDao +@Repository +@Transactional +public class HsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository { + + private static final String INSERT_ON_CONFLICT_DO_UPDATE = "MERGE INTO relation USING (VALUES :fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) R " + + "(from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " + + "ON (relation.from_id = R.from_id AND relation.from_type = R.from_type AND relation.relation_type_group = R.relation_type_group AND relation.relation_type = R.relation_type AND relation.to_id = R.to_id AND relation.to_type = R.to_type) " + + "WHEN MATCHED THEN UPDATE SET relation.additional_info = R.additional_info " + + "WHEN NOT MATCHED THEN INSERT (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) VALUES (R.from_id, R.from_type, R.to_id, R.to_type, R.relation_type_group, R.relation_type, R.additional_info)"; + + @Override + public RelationEntity saveOrUpdate(RelationEntity entity) { + return processSaveOrUpdate(entity); + } + + @Override + protected RelationEntity processSaveOrUpdate(RelationEntity entity) { + getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).executeUpdate(); + return entityManager.find(RelationEntity.class, new RelationCompositeKey(entity.toData())); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index cb7887a065..fdd48e7480 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -56,6 +56,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple @Autowired private RelationRepository relationRepository; + @Autowired + private RelationInsertRepository relationInsertRepository; + @Override public ListenableFuture> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { return service.submit(() -> DaoUtil.convertDataList( @@ -117,12 +120,12 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple @Override public boolean saveRelation(TenantId tenantId, EntityRelation relation) { - return relationRepository.save(new RelationEntity(relation)) != null; + return relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null; } @Override public ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation) { - return service.submit(() -> relationRepository.save(new RelationEntity(relation)) != null); + return service.submit(() -> relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java new file mode 100644 index 0000000000..dbef233811 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/PsqlRelationInsertRepository.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 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.relation; + +import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.RelationEntity; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.SqlDao; + +@PsqlDao +@SqlDao +@Repository +@Transactional +public class PsqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository { + + private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" + + " VALUES (:fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) " + + "ON CONFLICT (from_id, from_type, relation_type_group, relation_type, to_id, to_type) DO UPDATE SET additional_info = :additionalInfo returning *"; + + @Override + public RelationEntity saveOrUpdate(RelationEntity entity) { + return processSaveOrUpdate(entity); + } + + @Override + protected RelationEntity processSaveOrUpdate(RelationEntity entity) { + return (RelationEntity) getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).getSingleResult(); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java new file mode 100644 index 0000000000..fe7dfe05be --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 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.relation; + +import org.thingsboard.server.dao.model.sql.RelationEntity; + +public interface RelationInsertRepository { + + RelationEntity saveOrUpdate(RelationEntity entity); + +} \ No newline at end of file From 67fa64d448262188cbea6f7b75e04477a00734cf Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Wed, 4 Mar 2020 10:37:22 +0200 Subject: [PATCH 246/261] fix NPE --- .../dao/sql/relation/AbstractRelationInsertRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java index 1d8fd11e7b..1944673b56 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java @@ -31,7 +31,7 @@ public abstract class AbstractRelationInsertRepository implements RelationInsert protected Query getQuery(RelationEntity entity, String query) { Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class); - if (entity.getAdditionalInfo().isNull()) { + if (entity.getAdditionalInfo() == null) { nativeQuery.setParameter("additionalInfo", null); } else { nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().asText()); From 30ba274eca2c3e1c5798a0abe47f638c0b25fbc4 Mon Sep 17 00:00:00 2001 From: Dmytro Shvaika Date: Wed, 4 Mar 2020 13:47:23 +0200 Subject: [PATCH 247/261] fix setting additional_info --- .../dao/sql/relation/AbstractRelationInsertRepository.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java index 1944673b56..1f43959c2a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java @@ -34,7 +34,7 @@ public abstract class AbstractRelationInsertRepository implements RelationInsert if (entity.getAdditionalInfo() == null) { nativeQuery.setParameter("additionalInfo", null); } else { - nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().asText()); + nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().toString()); } return nativeQuery .setParameter("fromId", entity.getFromId()) From 29429cb0c42ea8ca8d5ecd74078a5afe574dec91 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 5 Mar 2020 13:11:14 +0200 Subject: [PATCH 248/261] Fix create color range --- ui/src/app/widget/lib/CanvasDigitalGauge.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index 3e914064a1..d450e09d9c 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -117,18 +117,18 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { if (levelColor !== null) { let percentage = isColorProperty ? inc * i : CanvasDigitalGauge.normalizeValue(levelColor.value, options.minValue, options.maxValue); let tColor = tinycolor(isColorProperty ? levelColor : levelColor.color); - options.colorsRange[i] = { + options.colorsRange.push({ pct: percentage, color: tColor.toRgb(), rgbString: tColor.toRgbString() - }; + }); if (options.neonGlowBrightness) { tColor = tinycolor(isColorProperty ? levelColor : levelColor.color).brighten(options.neonGlowBrightness); - options.neonColorsRange[i] = { + options.neonColorsRange.push({ pct: percentage, color: tColor.toRgb(), rgbString: tColor.toRgbString() - }; + }); } } } From b3aaf66f3fd01eb053fe9402ed5ee967eb5d947b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Mar 2020 18:19:05 +0200 Subject: [PATCH 249/261] created TbCheckAlarmStatusNode --- .../engine/filter/TbCheckAlarmStatusNode.java | 96 +++++++++++++++++++ .../filter/TbCheckAlarmStatusNodeConfig.java | 34 +++++++ 2 files changed, 130 insertions(+) create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java create mode 100644 rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java new file mode 100644 index 0000000000..30eeb9219a --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -0,0 +1,96 @@ +/** + * Copyright © 2016-2020 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.filter; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +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.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.annotation.Nullable; +import java.io.IOException; + +import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; + +@Slf4j +@RuleNode( + type = ComponentType.FILTER, + name = "checks alarm status", + configClazz = TbCheckAlarmStatusNodeConfig.class, + nodeDescription = "Checks alarm status.", + nodeDetails = "If the alarm status matches the specified one - msg is success if does not match - msg is failure.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbFilterNodeCheckAlarmStatusConfig") +public class TbCheckAlarmStatusNode implements TbNode { + private TbCheckAlarmStatusNodeConfig config; + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public void init(TbContext tbContext, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbCheckAlarmStatusNodeConfig.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + try { + Alarm alarm = mapper.readValue(msg.getData(), Alarm.class); + + ListenableFuture latest = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), alarm.getId()); + + Futures.addCallback(latest, new FutureCallback() { + @Override + public void onSuccess(@Nullable Alarm result) { + boolean isPresent = false; + for (String alarmStatus : config.getAlarmStatusList()) { + if (alarm.getStatus().name().equals(alarmStatus)) { + isPresent = true; + break; + } + } + + if (isPresent) { + ctx.tellNext(msg, SUCCESS); + } else { + ctx.tellNext(msg, FAILURE); + } + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }); + } catch (IOException e) { + log.error("Failed to parse alarm: [{}]", msg.getData()); + throw new TbNodeException(e); + } + } + + @Override + public void destroy() { + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java new file mode 100644 index 0000000000..81c1e22120 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2020 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.filter; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.List; + +@Data +public class TbCheckAlarmStatusNodeConfig implements NodeConfiguration { + private List alarmStatusList; + + @Override + public TbCheckAlarmStatusNodeConfig defaultConfiguration() { + TbCheckAlarmStatusNodeConfig config = new TbCheckAlarmStatusNodeConfig(); + config.setAlarmStatusList(Collections.emptyList()); + return config; + } +} From a4ae57eb86a329a3afe97c7658968ca2e450022d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Mar 2020 16:02:42 +0200 Subject: [PATCH 250/261] improvements --- .../engine/filter/TbCheckAlarmStatusNode.java | 26 ++++++++++++------- .../filter/TbCheckAlarmStatusNodeConfig.java | 7 ++--- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index 30eeb9219a..e0ec9300d4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -27,11 +27,14 @@ 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.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.annotation.Nullable; import java.io.IOException; +import java.util.UUID; import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @@ -41,6 +44,7 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; type = ComponentType.FILTER, name = "checks alarm status", configClazz = TbCheckAlarmStatusNodeConfig.class, + relationTypes = {"True", "False"}, nodeDescription = "Checks alarm status.", nodeDetails = "If the alarm status matches the specified one - msg is success if does not match - msg is failure.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -64,18 +68,22 @@ public class TbCheckAlarmStatusNode implements TbNode { Futures.addCallback(latest, new FutureCallback() { @Override public void onSuccess(@Nullable Alarm result) { - boolean isPresent = false; - for (String alarmStatus : config.getAlarmStatusList()) { - if (alarm.getStatus().name().equals(alarmStatus)) { - isPresent = true; - break; + if (result != null) { + boolean isPresent = false; + for (AlarmStatus alarmStatus : config.getAlarmStatusList()) { + if (alarm.getStatus() == alarmStatus) { + isPresent = true; + break; + } } - } - if (isPresent) { - ctx.tellNext(msg, SUCCESS); + if (isPresent) { + ctx.tellNext(msg, "True"); + } else { + ctx.tellNext(msg, "False"); + } } else { - ctx.tellNext(msg, FAILURE); + ctx.tellFailure(msg, new TbNodeException("No such Alarm found.")); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java index 81c1e22120..282027335a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNodeConfig.java @@ -17,18 +17,19 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.alarm.AlarmStatus; -import java.util.Collections; +import java.util.Arrays; import java.util.List; @Data public class TbCheckAlarmStatusNodeConfig implements NodeConfiguration { - private List alarmStatusList; + private List alarmStatusList; @Override public TbCheckAlarmStatusNodeConfig defaultConfiguration() { TbCheckAlarmStatusNodeConfig config = new TbCheckAlarmStatusNodeConfig(); - config.setAlarmStatusList(Collections.emptyList()); + config.setAlarmStatusList(Arrays.asList(AlarmStatus.ACTIVE_ACK, AlarmStatus.ACTIVE_UNACK)); return config; } } From ad4e27d08bab91bee27cbe28882fc3500bd08945 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 5 Mar 2020 16:35:27 +0200 Subject: [PATCH 251/261] added TbCheckAlarmStatusNode to rulenode-core-config.js --- .../public/static/rulenode/rulenode-core-config.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 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 0358f23448..9738a92ab7 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 }}
    tb.rulenode.relation-types-list-hint
    "},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.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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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}])); +!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(103)},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 }}
    tb.rulenode.relation-types-list-hint
    "},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.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 }} {{tlsVersion}}
    '},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.last-level-relation' | translate}}
    {{ ('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="
    {{'alarm.display-status.' + item | translate}} {{'alarm.display-status.' + $chip | translate}}
    "},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.last-level-relation' | translate}}
    {{ ('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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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(75),r=i(a),o=n(53),l=i(o),s=n(58),d=i(s),u=n(55),c=i(u),m=n(54),g=i(m),p=n(62),f=i(p),b=n(69),v=i(b),y=n(70),h=i(y),q=n(68),x=i(q),$=n(61),k=i($),T=n(73),C=i(T),w=n(74),M=i(w),N=n(67),S=i(N),_=n(63),E=i(_),F=n(72),P=i(F),A=n(65),V=i(A),I=n(64),j=i(I),O=n(52),D=i(O),L=n(76),R=i(L),K=n(57),U=i(K),z=n(56),H=i(z),B=n(71),G=i(B),Y=n(59),Q=i(Y),W=n(66),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",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",R.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.tlsVersions=["TLSv1.0","TLSv1.1","TLSv1.2","TLSv1.3"],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(84),r=i(a),o=n(85),l=i(o),s=n(80),d=i(s),u=n(86),c=i(u),m=n(79),g=i(m),p=n(87),f=i(p),b=n(82),v=i(b),y=n(81),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),n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.alarmStatusList=[];for(var s in t.alarmStatus)n.alarmStatusList.push(t.alarmStatus[s]);r.$render=function(){n.configuration=r.$viewValue},n.getAlarmStatusList=function(){return n.alarmStatusList.filter(function(e){return n.configuration.alarmStatusList.indexOf(e)===-1})},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","types"],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);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(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.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(41),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(42),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(95),r=i(a),o=n(93),l=i(o),s=n(96),d=i(s),u=n(90),c=i(u),m=n(94),g=i(m),p=n(89),f=i(p),b=n(91),v=i(b),y=n(88),h=i(y);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).directive("tbFilterNodeCheckAlarmStatusConfig",h.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(47),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(48),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(49),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(99),r=i(a),o=n(101),l=i(o),s=n(102),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(50),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(51),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(106),r=i(a),o=n(92),l=i(o),s=n(83),d=i(s),u=n(100),c=i(u),m=n(60),g=i(m),p=n(78),f=i(p),b=n(98),v=i(b),y=n(77),h=i(y),q=n(97),x=i(q),$=n(105),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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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","alarm-statuses-filter":"Alarm statuses filter","alarm-statuses-required":"Alarm statuses 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","tls-version":"TLS version","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(104),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 ae1e8cdc17197441a521cffdbe1173f37daff3fc Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 6 Mar 2020 11:06:01 +0200 Subject: [PATCH 252/261] Added env variable link --- 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 18b77c4f41..820fe14be1 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -300,7 +300,7 @@ caffeine: redis: # standalone or cluster connection: - type: standalone + type: "${REDIS_CONNECTION_TYPE:standalone}" standalone: host: "${REDIS_HOST:localhost}" port: "${REDIS_PORT:6379}" From d68ef2333ab8a1063ac027e5c5682edfa07e07bc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 10 Mar 2020 14:48:12 +0200 Subject: [PATCH 253/261] updated guava and protobuf versions --- .../server/actors/ActorSystemContext.java | 5 ++- .../device/DeviceActorMessageProcessor.java | 4 +- .../server/controller/DeviceController.java | 8 ++-- .../controller/EntityViewController.java | 3 +- .../controller/TelemetryController.java | 17 +++++---- .../AbstractNashornJsInvokeService.java | 8 ++-- .../service/script/RemoteJsInvokeService.java | 10 ++--- .../script/RuleNodeJsScriptEngine.java | 7 ++-- .../state/DefaultDeviceStateService.java | 22 ++++++++--- .../DefaultTelemetryWebSocketService.java | 18 ++++----- .../transport/LocalTransportApiService.java | 16 ++------ .../server/kafka/AsyncCallbackTemplate.java | 3 +- .../thingsboard/common/util/DonAsynchron.java | 11 +++--- .../server/dao/alarm/BaseAlarmService.java | 13 +++---- .../server/dao/alarm/CassandraAlarmDao.java | 13 ++++--- .../server/dao/asset/BaseAssetService.java | 34 +++++++++-------- .../server/dao/asset/CassandraAssetDao.java | 3 +- .../dashboard/CassandraDashboardInfoDao.java | 3 +- .../dao/dashboard/DashboardServiceImpl.java | 37 +++++++++--------- .../server/dao/device/CassandraDeviceDao.java | 5 ++- .../dao/device/ClaimDevicesServiceImpl.java | 9 +++-- .../server/dao/device/DeviceServiceImpl.java | 27 ++++++------- .../server/dao/entity/BaseEntityService.java | 13 ++++++- .../entityview/CassandraEntityViewDao.java | 5 ++- .../dao/entityview/EntityViewServiceImpl.java | 9 +++-- .../dao/event/CassandraBaseEventDao.java | 11 ++++-- .../dao/nosql/CassandraAbstractModelDao.java | 7 ++-- .../dao/nosql/RateLimitedResultSetFuture.java | 7 ++-- .../dao/relation/BaseRelationService.java | 38 ++++++++++++------- .../server/dao/sql/alarm/JpaAlarmDao.java | 5 ++- .../sql/dashboard/JpaDashboardInfoDao.java | 3 +- ...stractChunkedAggregationTimeseriesDao.java | 3 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 3 +- .../timescale/TimescaleTimeseriesDao.java | 3 +- .../CassandraBaseTimeseriesDao.java | 5 ++- .../nosql/RateLimitedResultSetFutureTest.java | 8 ++-- pom.xml | 4 +- .../action/TbAbstractRelationActionNode.java | 5 ++- .../rule/engine/action/TbClearAlarmNode.java | 7 ++-- .../rule/engine/action/TbCreateAlarmNode.java | 9 +++-- .../engine/action/TbCreateRelationNode.java | 7 ++-- .../engine/action/TbDeleteRelationNode.java | 14 ++++--- .../engine/filter/TbCheckAlarmStatusNode.java | 8 +--- .../engine/filter/TbCheckRelationNode.java | 5 ++- .../metadata/TbAbstractGetAttributesNode.java | 5 ++- .../TbAbstractGetEntityDetailsNode.java | 10 +++-- .../engine/metadata/TbEntityGetAttrNode.java | 9 +++-- .../metadata/TbGetCustomerDetailsNode.java | 9 +++-- .../metadata/TbGetOriginatorFieldsNode.java | 11 ++++-- .../metadata/TbGetTenantDetailsNode.java | 3 +- .../EntitiesAlarmOriginatorIdAsyncLoader.java | 3 +- .../util/EntitiesCustomerIdAsyncLoader.java | 10 +++-- .../util/EntitiesFieldsAsyncLoader.java | 3 +- .../EntitiesRelatedDeviceIdAsyncLoader.java | 4 +- .../EntitiesRelatedEntityIdAsyncLoader.java | 6 +-- .../util/EntitiesTenantIdAsyncLoader.java | 13 +++++-- 56 files changed, 308 insertions(+), 233 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 bf06a3a8ac..54ac41172c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; import lombok.Getter; @@ -469,7 +470,7 @@ public class ActorSystemContext { public void onFailure(Throwable th) { log.error("Could not save debug Event for Node", th); } - }); + }, MoreExecutors.directExecutor()); } catch (IOException ex) { log.warn("Failed to persist rule node debug message", ex); } @@ -522,7 +523,7 @@ public class ActorSystemContext { public void onFailure(Throwable th) { log.error("Could not save debug Event for Rule Chain", th); } - }); + }, MoreExecutors.directExecutor()); } public static Exception toException(Throwable error) { 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 acf1a3161c..2752b53090 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 @@ -20,9 +20,9 @@ import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; @@ -292,7 +292,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .build(); sendToTransport(responseMsg, sessionInfo); } - }); + }, MoreExecutors.directExecutor()); } private ListenableFuture>> getAttributesKvEntries(GetAttributeRequestMsg request) { 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 d147d27b40..23460506e9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -44,7 +46,6 @@ 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.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; @@ -425,11 +426,12 @@ public class DeviceController extends BaseController { deferredResult.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); } } + @Override public void onFailure(Throwable t) { deferredResult.setErrorResult(t); } - }); + }, MoreExecutors.directExecutor()); return deferredResult; } catch (Exception e) { throw handleException(e); @@ -466,7 +468,7 @@ public class DeviceController extends BaseController { public void onFailure(Throwable t) { deferredResult.setErrorResult(t); } - }); + }, MoreExecutors.directExecutor()); return deferredResult; } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index 2f146e5738..82053652d5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -158,7 +159,7 @@ public class EntityViewController extends BaseController { }); } return null; - }); + }, MoreExecutors.directExecutor()); } else { return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index e534ebf7c4..f607d57cf6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -22,6 +22,7 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonElement; import com.google.gson.JsonParseException; import com.google.gson.JsonParser; @@ -174,7 +175,7 @@ public class TelemetryController extends BaseController { public DeferredResult getTimeseriesKeys( @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException { return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, - (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result))); + (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); } @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @@ -210,7 +211,7 @@ public class TelemetryController extends BaseController { List queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg)) .collect(Collectors.toList()); - Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes)); + Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); }); } @@ -462,7 +463,7 @@ public class TelemetryController extends BaseController { } else { future = tsService.findLatest(user.getTenantId(), entityId, toKeysList(keys)); } - Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes)); + Futures.addCallback(future, getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor()); } private void getAttributeValuesCallback(@Nullable DeferredResult result, SecurityUser user, EntityId entityId, String scope, String keys) { @@ -470,9 +471,9 @@ public class TelemetryController extends BaseController { FutureCallback> callback = getAttributeValuesToResponseCallback(result, user, scope, entityId, keyList); if (!StringUtils.isEmpty(scope)) { if (keyList != null && !keyList.isEmpty()) { - Futures.addCallback(attributesService.find(user.getTenantId(), entityId, scope, keyList), callback); + Futures.addCallback(attributesService.find(user.getTenantId(), entityId, scope, keyList), callback, MoreExecutors.directExecutor()); } else { - Futures.addCallback(attributesService.findAll(user.getTenantId(), entityId, scope), callback); + Futures.addCallback(attributesService.findAll(user.getTenantId(), entityId, scope), callback, MoreExecutors.directExecutor()); } } else { List>> futures = new ArrayList<>(); @@ -486,12 +487,12 @@ public class TelemetryController extends BaseController { ListenableFuture> future = mergeAllAttributesFutures(futures); - Futures.addCallback(future, callback); + Futures.addCallback(future, callback, MoreExecutors.directExecutor()); } } private void getAttributeKeysCallback(@Nullable DeferredResult result, TenantId tenantId, EntityId entityId, String scope) { - Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), getAttributeKeysToResponseCallback(result)); + Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), getAttributeKeysToResponseCallback(result), MoreExecutors.directExecutor()); } private void getAttributeKeysCallback(@Nullable DeferredResult result, TenantId tenantId, EntityId entityId) { @@ -502,7 +503,7 @@ public class TelemetryController extends BaseController { ListenableFuture> future = mergeAllAttributesFutures(futures); - Futures.addCallback(future, getAttributeKeysToResponseCallback(result)); + Futures.addCallback(future, getAttributeKeysToResponseCallback(result), MoreExecutors.directExecutor()); } private FutureCallback> getTsKeysToResponseCallback(final DeferredResult response) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index e4901785fe..49a6304ebb 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -18,6 +18,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 com.google.common.util.concurrent.MoreExecutors; import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandboxes; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; @@ -28,20 +29,17 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.script.Invocable; import javax.script.ScriptEngine; import javax.script.ScriptException; import java.util.UUID; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @Slf4j @@ -140,7 +138,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer if (maxRequestsTimeout > 0) { result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); } - Futures.addCallback(result, evalCallback); + Futures.addCallback(result, evalCallback, MoreExecutors.directExecutor()); return result; } @@ -163,7 +161,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer if (maxRequestsTimeout > 0) { result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); } - Futures.addCallback(result, invokeCallback); + Futures.addCallback(result, invokeCallback, MoreExecutors.directExecutor()); return result; } 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 45d773f387..1a218b119b 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 @@ -18,6 +18,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 com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -40,7 +41,6 @@ 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; @Slf4j @ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "remote", matchIfMissing = true) @@ -166,7 +166,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } kafkaFailedMsgs.incrementAndGet(); } - }); + }, MoreExecutors.directExecutor()); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); UUID compiledScriptId = new UUID(compilationResult.getScriptIdMSB(), compilationResult.getScriptIdLSB()); @@ -178,7 +178,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.debug("[{}] Failed to compile script due to [{}]: {}", compiledScriptId, compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); throw new RuntimeException(compilationResult.getErrorDetails()); } - }); + }, MoreExecutors.directExecutor()); } @Override @@ -217,7 +217,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } kafkaFailedMsgs.incrementAndGet(); } - }); + }, MoreExecutors.directExecutor()); return Futures.transform(future, response -> { JsInvokeProtos.JsInvokeResponse invokeResult = response.getInvokeResponse(); if (invokeResult.getSuccess()) { @@ -226,7 +226,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw new RuntimeException(invokeResult.getErrorDetails()); } - }); + }, MoreExecutors.directExecutor()); } @Override 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 73bdd27b78..ef5d4716cb 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 @@ -21,6 +21,7 @@ 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 com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -121,7 +122,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } else { return Futures.immediateFuture(unbindMsg(json, msg)); } - }); + }, MoreExecutors.directExecutor()); } @Override @@ -174,7 +175,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } else { return Futures.immediateFuture(json.asBoolean()); } - }); + }, MoreExecutors.directExecutor()); } @Override @@ -232,7 +233,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return Futures.immediateFailedFuture(new ScriptException(e)); } } - }); + }, MoreExecutors.directExecutor()); } public void destroy() { 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 f40de180ab..18726e3a5a 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 @@ -64,14 +64,26 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.*; +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.Random; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.common.data.DataConstants.*; +import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; +import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; /** * Created by ashvayka on 01.05.18. @@ -401,7 +413,7 @@ public class DefaultDeviceStateService implements DeviceStateService { public void onFailure(Throwable t) { log.warn("Failed to register device to the state service", t); } - }); + }, MoreExecutors.directExecutor()); } else { sendDeviceEvent(device.getTenantId(), device.getId(), address.get(), true, false, false); } @@ -456,10 +468,10 @@ public class DefaultDeviceStateService implements DeviceStateService { private ListenableFuture fetchDeviceState(Device device) { if (persistToTelemetry) { ListenableFuture> tsData = tsService.findLatest(TenantId.SYS_TENANT_ID, device.getId(), PERSISTENT_ATTRIBUTES); - return Futures.transform(tsData, extractDeviceStateData(device)); + return Futures.transform(tsData, extractDeviceStateData(device), MoreExecutors.directExecutor()); } else { ListenableFuture> attrData = attributesService.find(TenantId.SYS_TENANT_ID, device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); - return Futures.transform(attrData, extractDeviceStateData(device)); + return Futures.transform(attrData, extractDeviceStateData(device), MoreExecutors.directExecutor()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index c4fe716ad9..741efe6f8a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -21,6 +21,7 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -54,9 +55,6 @@ import org.thingsboard.server.service.telemetry.cmd.SubscriptionCmd; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmd; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.telemetry.cmd.TimeseriesSubscriptionCmd; -import org.thingsboard.server.service.telemetry.exception.AccessDeniedException; -import org.thingsboard.server.service.telemetry.exception.EntityNotFoundException; -import org.thingsboard.server.service.telemetry.exception.InternalErrorException; import org.thingsboard.server.service.telemetry.exception.UnauthorizedException; import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; import org.thingsboard.server.service.telemetry.sub.SubscriptionState; @@ -70,12 +68,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -616,7 +616,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } ListenableFuture> future = mergeAllAttributesFutures(futures); - Futures.addCallback(future, callback); + Futures.addCallback(future, callback, MoreExecutors.directExecutor()); } @Override @@ -630,7 +630,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return new FutureCallback() { @Override public void onSuccess(@Nullable ValidationResult result) { - Futures.addCallback(attributesService.find(tenantId, entityId, scope, keys), callback); + Futures.addCallback(attributesService.find(tenantId, entityId, scope, keys), callback, MoreExecutors.directExecutor()); } @Override @@ -650,7 +650,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } ListenableFuture> future = mergeAllAttributesFutures(futures); - Futures.addCallback(future, callback); + Futures.addCallback(future, callback, MoreExecutors.directExecutor()); } @Override @@ -664,7 +664,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi return new FutureCallback() { @Override public void onSuccess(@Nullable ValidationResult result) { - Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), callback); + Futures.addCallback(attributesService.findAll(tenantId, entityId, scope), callback, MoreExecutors.directExecutor()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java index 37ff8ed93d..8e820fbffe 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java @@ -19,10 +19,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; 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.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; @@ -42,19 +41,10 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponse import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import org.thingsboard.server.kafka.TBKafkaConsumerTemplate; -import org.thingsboard.server.kafka.TBKafkaProducerTemplate; -import org.thingsboard.server.kafka.TbKafkaResponseTemplate; -import org.thingsboard.server.kafka.TbKafkaSettings; -import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.state.DeviceStateService; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.UUID; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.locks.ReentrantLock; /** @@ -145,7 +135,7 @@ public class LocalTransportApiService implements TransportApiService { try { ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder(); builder.setDeviceInfo(getDeviceInfoProto(device)); - if(!StringUtils.isEmpty(credentials.getCredentialsValue())){ + if (!StringUtils.isEmpty(credentials.getCredentialsValue())) { builder.setCredentialsBody(credentials.getCredentialsValue()); } return TransportApiResponseMsg.newBuilder() @@ -154,7 +144,7 @@ public class LocalTransportApiService implements TransportApiService { log.warn("[{}] Failed to lookup device by id", deviceId, e); return getEmptyTransportApiResponse(); } - }); + }, MoreExecutors.directExecutor()); } private DeviceInfoProto getDeviceInfoProto(Device device) throws JsonProcessingException { diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java index 17599bfccb..a01411e48f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/AsyncCallbackTemplate.java @@ -18,6 +18,7 @@ package org.thingsboard.server.kafka; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.Executor; import java.util.concurrent.ScheduledExecutorService; @@ -59,7 +60,7 @@ public class AsyncCallbackTemplate { if (executor != null) { Futures.addCallback(future, callback, executor); } else { - Futures.addCallback(future, callback); + Futures.addCallback(future, callback, MoreExecutors.directExecutor()); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java b/common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java index 3557fcb40a..0940878ab2 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java +++ b/common/util/src/main/java/org/thingsboard/common/util/DonAsynchron.java @@ -18,19 +18,20 @@ package org.thingsboard.common.util; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.Executor; import java.util.function.Consumer; public class DonAsynchron { - public static void withCallback(ListenableFuture future, Consumer onSuccess, - Consumer onFailure) { + public static void withCallback(ListenableFuture future, Consumer onSuccess, + Consumer onFailure) { withCallback(future, onSuccess, onFailure, null); } - public static void withCallback(ListenableFuture future, Consumer onSuccess, - Consumer onFailure, Executor executor) { + public static void withCallback(ListenableFuture future, Consumer onSuccess, + Consumer onFailure, Executor executor) { FutureCallback callback = new FutureCallback() { @Override public void onSuccess(T result) { @@ -49,7 +50,7 @@ public class DonAsynchron { if (executor != null) { Futures.addCallback(future, callback, executor); } else { - Futures.addCallback(future, callback); + Futures.addCallback(future, callback, MoreExecutors.directExecutor()); } } } 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 d86e7e0f1b..453f1ea0be 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 @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -53,7 +54,6 @@ import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; @@ -264,9 +264,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { alarmInfo.setOriginatorName(originatorName); return alarmInfo; - } - ); - }); + }, MoreExecutors.directExecutor()); + }, MoreExecutors.directExecutor()); } @Override @@ -283,11 +282,11 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } alarmInfo.setOriginatorName(originatorName); return alarmInfo; - } + }, MoreExecutors.directExecutor() )); } return Futures.successfulAsList(alarmFutures); - }); + }, MoreExecutors.directExecutor()); } return Futures.transform(alarms, new Function, TimePageData>() { @Nullable @@ -295,7 +294,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ public TimePageData apply(@Nullable List alarms) { return new TimePageData<>(alarms, query.getPageLink()); } - }); + }, MoreExecutors.directExecutor()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java index f76ab871a4..e124b3e172 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java @@ -20,6 +20,7 @@ import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -82,10 +83,10 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao - assetList == null ? Collections.emptyList() : assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList()) + assetList == null ? Collections.emptyList() : assetList.stream().filter(asset -> query.getAssetTypes().contains(asset.getType())).collect(Collectors.toList()), MoreExecutors.directExecutor() ); return assets; } @@ -274,7 +276,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ assetTypes -> { assetTypes.sort(Comparator.comparing(EntitySubtype::getType)); return assetTypes; - }); + }, MoreExecutors.directExecutor()); } private DataValidator assetValidator = @@ -335,18 +337,18 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ }; private PaginatedRemover tenantAssetsRemover = - new PaginatedRemover() { + new PaginatedRemover() { - @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { - return assetDao.findAssetsByTenantId(id.getId(), pageLink); - } + @Override + protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + return assetDao.findAssetsByTenantId(id.getId(), pageLink); + } - @Override - protected void removeEntity(TenantId tenantId, Asset entity) { - deleteAsset(tenantId, new AssetId(entity.getId().getId())); - } - }; + @Override + protected void removeEntity(TenantId tenantId, Asset entity) { + deleteAsset(tenantId, new AssetId(entity.getId().getId())); + } + }; private PaginatedRemover customerAssetsUnasigner = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java index 9f3bac983e..9808e7b117 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/CassandraAssetDao.java @@ -23,6 +23,7 @@ import com.datastax.driver.mapping.Result; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; @@ -185,7 +186,7 @@ public class CassandraAssetDao extends CassandraAbstractSearchTextDao apply(@Nullable List dashboards) { return new TimePageData<>(dashboards, pageLink); } - }); + }, MoreExecutors.directExecutor()); } @Override @@ -244,24 +245,24 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } } } - }; - + }; + private PaginatedRemover tenantDashboardsRemover = new PaginatedRemover() { - - @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { - return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink); - } - @Override - protected void removeEntity(TenantId tenantId, DashboardInfo entity) { - deleteDashboard(tenantId, new DashboardId(entity.getUuidId())); - } - }; - + @Override + protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink); + } + + @Override + protected void removeEntity(TenantId tenantId, DashboardInfo entity) { + deleteDashboard(tenantId, new DashboardId(entity.getUuidId())); + } + }; + private class CustomerDashboardsUnassigner extends TimePaginatedRemover { - + private Customer customer; CustomerDashboardsUnassigner(Customer customer) { @@ -282,7 +283,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb protected void removeEntity(TenantId tenantId, DashboardInfo entity) { unassignDashboardFromCustomer(customer.getTenantId(), new DashboardId(entity.getUuidId()), this.customer.getId()); } - + } private class CustomerDashboardsUpdater extends TimePaginatedRemover { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java index e7becfa1ad..a01725fe52 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java @@ -23,6 +23,7 @@ import com.datastax.driver.mapping.Result; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Device; @@ -178,14 +179,14 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao entitySubtypes = new ArrayList<>(); result.all().forEach((entitySubtypeEntity) -> - entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) + entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) ); return entitySubtypes; } else { return Collections.emptyList(); } } - }); + }, MoreExecutors.directExecutor()); } } 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 abd453cc05..0bfc8885be 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 @@ -18,6 +18,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 com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -97,9 +98,9 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { } log.warn("Failed to find claimingAllowed attribute for device or it is already claimed![{}]", device.getName()); throw new IllegalArgumentException(); - }); + }, MoreExecutors.directExecutor()); } - }); + }, MoreExecutors.directExecutor()); } private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException { @@ -138,9 +139,9 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { device.setCustomerId(customerId); Device savedDevice = deviceService.saveDevice(device); - return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); } - return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED)); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); } } else { log.warn("Failed to find the device's claiming message![{}]", device.getName()); 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 dbcbac73b8..db998b7328 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -291,7 +292,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } } return Futures.successfulAsList(futures); - }); + }, MoreExecutors.directExecutor()); devices = Futures.transform(devices, new Function, List>() { @Nullable @@ -299,7 +300,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe public List apply(@Nullable List deviceList) { return deviceList == null ? Collections.emptyList() : deviceList.stream().filter(device -> query.getDeviceTypes().contains(device.getType())).collect(Collectors.toList()); } - }); + }, MoreExecutors.directExecutor()); return devices; } @@ -313,7 +314,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceTypes -> { deviceTypes.sort(Comparator.comparing(EntitySubtype::getType)); return deviceTypes; - }); + }, MoreExecutors.directExecutor()); } private DataValidator deviceValidator = @@ -374,18 +375,18 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe }; private PaginatedRemover tenantDevicesRemover = - new PaginatedRemover() { + new PaginatedRemover() { - @Override - protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { - return deviceDao.findDevicesByTenantId(id.getId(), pageLink); - } + @Override + protected List findEntities(TenantId tenantId, TenantId id, TextPageLink pageLink) { + return deviceDao.findDevicesByTenantId(id.getId(), pageLink); + } - @Override - protected void removeEntity(TenantId tenantId, Device entity) { - deleteDevice(tenantId, new DeviceId(entity.getUuidId())); - } - }; + @Override + protected void removeEntity(TenantId tenantId, Device entity) { + deleteDevice(tenantId, new DeviceId(entity.getUuidId())); + } + }; private PaginatedRemover customerDeviceUnasigner = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index c49fcc3728..5867e505b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -18,12 +18,21 @@ package org.thingsboard.server.dao.entity; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.id.*; +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.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.customer.CustomerService; @@ -109,7 +118,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe default: throw new IllegalStateException("Not Implemented!"); } - entityName = Futures.transform(hasName, (Function) hasName1 -> hasName1 != null ? hasName1.getName() : null ); + entityName = Futures.transform(hasName, (Function) hasName1 -> hasName1 != null ? hasName1.getName() : null, MoreExecutors.directExecutor()); return entityName; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java index aabc2c172a..05fac418d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/CassandraEntityViewDao.java @@ -23,6 +23,7 @@ import com.datastax.driver.mapping.Result; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; @@ -97,7 +98,7 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao entityViewEntities = findPageWithTextSearch(new TenantId(tenantId), ENTITY_VIEW_BY_TENANT_AND_SEARCH_TEXT_CF, - Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink); + Collections.singletonList(eq(TENANT_ID_PROPERTY, tenantId)), pageLink); log.trace("Found entity views [{}] by tenantId [{}] and pageLink [{}]", entityViewEntities, tenantId, pageLink); return DaoUtil.convertDataList(entityViewEntities); @@ -181,6 +182,6 @@ public class CassandraEntityViewDao extends CassandraAbstractSearchTextDao, List>() { @Nullable @@ -207,7 +208,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti public List apply(@Nullable List entityViewList) { return entityViewList == null ? Collections.emptyList() : entityViewList.stream().filter(entityView -> query.getEntityViewTypes().contains(entityView.getType())).collect(Collectors.toList()); } - }); + }, MoreExecutors.directExecutor()); return entityViews; } @@ -246,7 +247,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti public void onFailure(Throwable t) { log.error("Error while finding entity views by tenantId and entityId", t); } - }); + }, MoreExecutors.directExecutor()); return entityViewsFuture; } } @@ -279,7 +280,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti entityViewTypes -> { entityViewTypes.sort(Comparator.comparing(EntitySubtype::getType)); return entityViewTypes; - }); + }, MoreExecutors.directExecutor()); } private DataValidator entityViewValidator = diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java index 1c93a5df07..bdd0201aa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/CassandraBaseEventDao.java @@ -22,6 +22,7 @@ import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; @@ -45,10 +46,12 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; -import static com.datastax.driver.core.querybuilder.QueryBuilder.in; import static com.datastax.driver.core.querybuilder.QueryBuilder.select; import static com.datastax.driver.core.querybuilder.QueryBuilder.ttl; -import static org.thingsboard.server.dao.model.ModelConstants.*; +import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BY_ID_VIEW_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.EVENT_BY_TYPE_AND_ID_VIEW_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.EVENT_COLUMN_FAMILY_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @Component @Slf4j @@ -96,7 +99,7 @@ public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao> optionalSave = saveAsync(event.getTenantId(), new EventEntity(event), false, eventsTtl); - return Futures.transform(optionalSave, opt -> opt.orElse(null)); + return Futures.transform(optionalSave, opt -> opt.orElse(null), MoreExecutors.directExecutor()); } @Override @@ -210,6 +213,6 @@ public class CassandraBaseEventDao extends CassandraAbstractSearchTimeDao, D> exte return Collections.emptyList(); } } - }); + }, MoreExecutors.directExecutor()); } return Futures.immediateFuture(Collections.emptyList()); } @@ -120,7 +121,7 @@ public abstract class CassandraAbstractModelDao, D> exte return null; } } - }); + }, MoreExecutors.directExecutor()); } return Futures.immediateFuture(null); } @@ -191,5 +192,5 @@ public abstract class CassandraAbstractModelDao, D> exte List entities = findListByStatement(tenantId, QueryBuilder.select().all().from(getColumnFamilyName()).setConsistencyLevel(cluster.getDefaultReadConsistencyLevel())); return DaoUtil.convertDataList(entities); } - + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java index cb30a7f48b..ebbe451b01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFuture.java @@ -22,6 +22,7 @@ import com.datastax.driver.core.Statement; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.Uninterruptibles; import org.thingsboard.server.dao.exception.BufferLimitException; import org.thingsboard.server.dao.util.AsyncRateLimiter; @@ -44,9 +45,9 @@ public class RateLimitedResultSetFuture implements ResultSetFuture { rateLimiter.release(); } return Futures.immediateFailedFuture(t); - }); + }, MoreExecutors.directExecutor()); this.originalFuture = Futures.transform(rateLimitFuture, - i -> executeAsyncWithRelease(rateLimiter, session, statement)); + i -> executeAsyncWithRelease(rateLimiter, session, statement), MoreExecutors.directExecutor()); } @@ -145,7 +146,7 @@ public class RateLimitedResultSetFuture implements ResultSetFuture { public void onFailure(Throwable t) { rateLimiter.release(); } - }); + }, MoreExecutors.directExecutor()); return resultSetFuture; } catch (RuntimeException re) { rateLimiter.release(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 68b7fce6a8..22b2543489 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -16,7 +16,10 @@ package org.thingsboard.server.dao.relation; import com.google.common.base.Function; -import com.google.common.util.concurrent.*; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; @@ -206,17 +209,20 @@ public class BaseRelationService implements RelationService { relations -> { List> results = deleteRelationGroupsAsync(tenantId, relations, cache, true); return Futures.allAsList(results); - }); + }, MoreExecutors.directExecutor()); ListenableFuture> outboundDeletions = Futures.transformAsync(outboundRelations, relations -> { List> results = deleteRelationGroupsAsync(tenantId, relations, cache, false); return Futures.allAsList(results); - }); + }, MoreExecutors.directExecutor()); ListenableFuture>> deletionsFuture = Futures.allAsList(inboundDeletions, outboundDeletions); - return Futures.transform(Futures.transformAsync(deletionsFuture, (deletions) -> relationDao.deleteOutboundRelationsAsync(tenantId, entityId)), result -> null); + return Futures.transform(Futures.transformAsync(deletionsFuture, + (deletions) -> relationDao.deleteOutboundRelationsAsync(tenantId, entityId), + MoreExecutors.directExecutor()), + result -> null, MoreExecutors.directExecutor()); } private List> deleteRelationGroupsAsync(TenantId tenantId, List> relations, Cache cache, boolean deleteFromDb) { @@ -306,9 +312,11 @@ public class BaseRelationService implements RelationService { public void onSuccess(@Nullable List result) { cache.putIfAbsent(fromAndTypeGroup, result); } + @Override - public void onFailure(Throwable t) {} - }); + public void onFailure(Throwable t) { + } + }, MoreExecutors.directExecutor()); return relationsFuture; } } @@ -328,7 +336,7 @@ public class BaseRelationService implements RelationService { EntityRelationInfo::setToName)) ); return Futures.successfulAsList(futures); - }); + }, MoreExecutors.directExecutor()); } @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}") @@ -385,9 +393,11 @@ public class BaseRelationService implements RelationService { public void onSuccess(@Nullable List result) { cache.putIfAbsent(toAndTypeGroup, result); } + @Override - public void onFailure(Throwable t) {} - }); + public void onFailure(Throwable t) { + } + }, MoreExecutors.directExecutor()); return relationsFuture; } } @@ -407,7 +417,7 @@ public class BaseRelationService implements RelationService { EntityRelationInfo::setFromName)) ); return Futures.successfulAsList(futures); - }); + }, MoreExecutors.directExecutor()); } private ListenableFuture fetchRelationInfoAsync(TenantId tenantId, EntityRelation relation, @@ -418,7 +428,7 @@ public class BaseRelationService implements RelationService { EntityRelationInfo entityRelationInfo1 = new EntityRelationInfo(relation); entityNameSetter.accept(entityRelationInfo1, entityName1); return entityRelationInfo1; - }); + }, MoreExecutors.directExecutor()); } @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}") @@ -466,7 +476,7 @@ public class BaseRelationService implements RelationService { } } return relations; - }); + }, MoreExecutors.directExecutor()); } catch (Exception e) { log.warn("Failed to query relations: [{}]", query, e); throw new RuntimeException(e); @@ -493,7 +503,7 @@ public class BaseRelationService implements RelationService { })) ); return Futures.successfulAsList(futures); - }); + }, MoreExecutors.directExecutor()); } protected void validate(EntityRelation relation) { @@ -600,7 +610,7 @@ public class BaseRelationService implements RelationService { } //TODO: try to remove this blocking operation List> relations = Futures.successfulAsList(futures).get(); - if (fetchLastLevelOnly && lvl > 0){ + if (fetchLastLevelOnly && lvl > 0) { children.clear(); } relations.forEach(r -> r.forEach(children::add)); 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 e00d73444b..356781d297 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.alarm; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -108,9 +109,9 @@ public class JpaAlarmDao extends JpaAbstractDao implements A for (EntityRelation relation : input) { alarmFutures.add(Futures.transform( findAlarmByIdAsync(tenantId, relation.getTo().getId()), - AlarmInfo::new)); + AlarmInfo::new, MoreExecutors.directExecutor())); } return Futures.successfulAsList(alarmFutures); - }); + }, MoreExecutors.directExecutor()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 64fe4af86b..aa80e8b5e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.dashboard; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -91,6 +92,6 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao> entitiesFutures) { 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 16828ee8e2..a9277ec7e2 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 @@ -20,6 +20,7 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -235,7 +236,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx public void onFailure(Throwable t) { log.warn("[{}] Failed to process remove of the latest value", entityId, t); } - }); + }, MoreExecutors.directExecutor()); return resultFuture; } 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 176bc712e8..4ca53a337b 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sqlts.timescale; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -143,7 +144,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } else { return Collections.emptyList(); } - }); + }, MoreExecutors.directExecutor()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 8fc8b4ab8a..b96462e350 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -28,6 +28,7 @@ import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -330,7 +331,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem stmt.setInt(6, (int) ttl); } futures.add(getFuture(executeAsyncWrite(tenantId, stmt), rs -> null)); - return Futures.transform(Futures.allAsList(futures), result -> null); + return Futures.transform(Futures.allAsList(futures), result -> null, MoreExecutors.directExecutor()); } private void processSetNullValues(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl, List> futures, long partition, DataType type) { @@ -545,7 +546,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem public void onFailure(Throwable t) { log.warn("[{}] Failed to process remove of the latest value", entityId, t); } - }); + }, MoreExecutors.directExecutor()); return resultFuture; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java index 76847c0e80..bb4a08a8f1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/nosql/RateLimitedResultSetFutureTest.java @@ -119,7 +119,7 @@ public class RateLimitedResultSetFutureTest { resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); - ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor()); Row actualRow = transform.get(); assertSame(row, actualRow); @@ -132,7 +132,7 @@ public class RateLimitedResultSetFutureTest { when(rateLimiter.acquireAsync()).thenReturn(Futures.immediateFuture(null)); when(session.executeAsync(statement)).thenThrow(new UnsupportedFeatureException(ProtocolVersion.V3, "hjg")); resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); - ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor()); try { transform.get(); fail(); @@ -156,7 +156,7 @@ public class RateLimitedResultSetFutureTest { when(realFuture.get()).thenThrow(new ExecutionException("Fail", new TimeoutException("timeout"))); resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); - ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor()); try { transform.get(); fail(); @@ -177,7 +177,7 @@ public class RateLimitedResultSetFutureTest { when(rateLimiter.acquireAsync()).thenReturn(future); resultSetFuture = new RateLimitedResultSetFuture(session, rateLimiter, statement); - ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one); + ListenableFuture transform = Futures.transform(resultSetFuture, ResultSet::one, MoreExecutors.directExecutor()); // TimeUnit.MILLISECONDS.sleep(200); future.cancel(false); latch.countDown(); diff --git a/pom.xml b/pom.xml index 547c97448f..ab7e658abf 100755 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 3.6.0 3.5.0.1 1.2.7 - 21.0 + 28.2-jre 2.6.1 3.4 1.6 @@ -63,7 +63,7 @@ 1.4.3 4.2.0 3.5.5 - 3.6.1 + 3.11.4 1.22.1 1.16.18 1.1.0 diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index bcfabf1100..3d2cb5d8c8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -20,6 +20,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -54,9 +55,9 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.TimeUnit; +import static org.thingsboard.common.util.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; -import static org.thingsboard.common.util.DonAsynchron.withCallback; @Slf4j public abstract class TbAbstractRelationActionNode implements TbNode { @@ -86,7 +87,7 @@ public abstract class TbAbstractRelationActionNode processEntityRelationAction(TbContext ctx, TbMsg msg) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer)); + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer), MoreExecutors.directExecutor()); } protected abstract boolean createEntityIfNotExists(); 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 d787d6b7ab..63fb59bed5 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 @@ -18,12 +18,13 @@ package org.thingsboard.rule.engine.action; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; 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.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -80,8 +81,8 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) { ListenableFuture asyncAlarm; if (msgAlarm != null) { - asyncAlarm = Futures.immediateCheckedFuture(msgAlarm); + asyncAlarm = Futures.immediateFuture(msgAlarm); } else { ctx.logJsEvalRequest(); asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null), details -> { ctx.logJsEvalResponse(); return buildAlarm(msg, details, ctx.getTenantId()); - }); + }, MoreExecutors.directExecutor()); } ListenableFuture asyncCreated = Futures.transform(asyncAlarm, alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor()); - return Futures.transform(asyncCreated, alarm -> new AlarmResult(true, false, false, alarm)); + return Futures.transform(asyncCreated, alarm -> new AlarmResult(true, false, false, alarm), MoreExecutors.directExecutor()); } private ListenableFuture updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) { @@ -140,7 +141,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode new AlarmResult(false, true, false, a)); + return Futures.transform(asyncUpdated, a -> new AlarmResult(false, true, false, a), MoreExecutors.directExecutor()); } private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java index ee6966f588..de74551c22 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.action; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -81,7 +82,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { @@ -120,7 +121,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode false); + return Futures.transform(Futures.allAsList(list), result -> false, MoreExecutors.directExecutor()); } return Futures.immediateFuture(false); }, ctx.getDbCallbackExecutor()); @@ -161,7 +162,7 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java index 671829f63f..9af3708fcd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.action; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -66,17 +67,18 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { - return Futures.transform(processSingle(ctx, msg, entityContainer), result -> new RelationContainer(msg, result)); + return Futures.transform(processSingle(ctx, msg, entityContainer), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); } private ListenableFuture getRelationContainerListenableFuture(TbContext ctx, TbMsg msg) { relationType = processPattern(msg, config.getRelationType()); if (config.isDeleteForSingleEntity()) { - return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer)); + return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer), MoreExecutors.directExecutor()); } else { - return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result)); + return Futures.transform(processList(ctx, msg), result -> new RelationContainer(msg, result), MoreExecutors.directExecutor()); } } + private ListenableFuture processList(TbContext ctx, TbMsg msg) { return Futures.transformAsync(processListSearchDirection(ctx, msg), entityRelations -> { if (entityRelations.isEmpty()) { @@ -93,9 +95,9 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) { @@ -106,7 +108,7 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java index e0ec9300d4..086e3aded0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckAlarmStatusNode.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -27,17 +28,12 @@ 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.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import javax.annotation.Nullable; import java.io.IOException; -import java.util.UUID; - -import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @Slf4j @RuleNode( @@ -91,7 +87,7 @@ public class TbCheckAlarmStatusNode implements TbNode { public void onFailure(Throwable t) { ctx.tellFailure(msg, t); } - }); + }, MoreExecutors.directExecutor()); } catch (IOException e) { log.error("Failed to parse alarm: [{}]", msg.getData()); throw new TbNodeException(e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java index 8fb64bbadf..89cd986b7e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -87,10 +88,10 @@ public class TbCheckRelationNode implements TbNode { private ListenableFuture processList(TbContext ctx, TbMsg msg) { if (EntitySearchDirection.FROM.name().equals(config.getDirection())) { return Futures.transformAsync(ctx.getRelationService() - .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList); + .findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); } else { return Futures.transformAsync(ctx.getRelationService() - .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList); + .findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList, MoreExecutors.directExecutor()); } } 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 be4e6b4e30..0bf1c23cc7 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 @@ -21,6 +21,7 @@ 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 com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonParseException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -122,7 +123,7 @@ public abstract class TbAbstractGetAttributesNode putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, ConcurrentHashMap> failuresMap) { @@ -152,7 +153,7 @@ public abstract class TbAbstractGetAttributesNode implements TbNode { private static final Gson gson = new Gson(); private static final JsonParser jsonParser = new JsonParser(); - private static final Type TYPE = new TypeToken>() {}.getType(); + private static final Type TYPE = new TypeToken>() { + }.getType(); protected C config; @@ -104,7 +106,7 @@ public abstract class TbAbstractGetEntityDetailsNode addContactProperties(JsonElement data, ListenableFuture entityFuture, EntityDetails entityDetails, String prefix) { @@ -114,7 +116,7 @@ public abstract class TbAbstractGetEntityDetailsNode implements TbNode } private void safeGetAttributes(TbContext ctx, TbMsg msg, T entityId) { - if(entityId == null || entityId.isNullUid()) { + if (entityId == null || entityId.isNullUid()) { ctx.tellNext(msg, FAILURE); return; } @@ -73,13 +74,13 @@ public abstract class TbEntityGetAttrNode implements TbNode private ListenableFuture> getAttributesAsync(TbContext ctx, EntityId entityId) { ListenableFuture> latest = ctx.getAttributesService().find(ctx.getTenantId(), entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); return Futures.transform(latest, l -> - l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList()), MoreExecutors.directExecutor()); } private ListenableFuture> getLatestTelemetry(TbContext ctx, EntityId entityId) { ListenableFuture> latest = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), entityId, config.getAttrMapping().keySet()); return Futures.transform(latest, l -> - l.stream().map(i -> (KvEntry) i).collect(Collectors.toList())); + l.stream().map(i -> (KvEntry) i).collect(Collectors.toList()), MoreExecutors.directExecutor()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 861e7330c5..f185d02965 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; @@ -63,7 +64,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode getCustomer(TbContext ctx, TbMsg msg) { @@ -79,7 +80,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { if (asset != null) { @@ -91,7 +92,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { if (entityView != null) { @@ -103,7 +104,7 @@ public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode { return in != null ? Futures.immediateFuture(in.getOriginator()) : Futures.immediateFuture(null); - }); + }, MoreExecutors.directExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java index ae1b54fffd..602ea8452b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java @@ -15,13 +15,17 @@ */ package org.thingsboard.rule.engine.util; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.HasCustomerId; -import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.UserId; public class EntitiesCustomerIdAsyncLoader { @@ -44,6 +48,6 @@ public class EntitiesCustomerIdAsyncLoader { private static ListenableFuture getCustomerAsync(ListenableFuture future) { return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(in.getCustomerId()) - : Futures.immediateFuture(null)); + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java index 74d586e1a7..a0a1c8629f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesFieldsAsyncLoader.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.util; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.BaseData; @@ -66,6 +67,6 @@ public class EntitiesFieldsAsyncLoader { ListenableFuture future, Function converter) { return Futures.transformAsync(future, in -> in != null ? Futures.immediateFuture(converter.apply(in)) - : Futures.immediateFailedFuture(new RuntimeException("Entity not found!"))); + : Futures.immediateFailedFuture(new RuntimeException("Entity not found!")), MoreExecutors.directExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java index b264bede07..e06113df8e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedDeviceIdAsyncLoader.java @@ -15,9 +15,9 @@ */ package org.thingsboard.rule.engine.util; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.DeviceRelationsQuery; @@ -40,7 +40,7 @@ public class EntitiesRelatedDeviceIdAsyncLoader { ListenableFuture> asyncDevices = deviceService.findDevicesByQuery(ctx.getTenantId(), query); return Futures.transformAsync(asyncDevices, d -> CollectionUtils.isNotEmpty(d) ? Futures.immediateFuture(d.get(0).getId()) - : Futures.immediateFuture(null)); + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } private static DeviceSearchQuery buildQuery(EntityId originator, DeviceRelationsQuery deviceRelationsQuery) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java index 39b2817761..a478b6b903 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java @@ -15,9 +15,9 @@ */ package org.thingsboard.rule.engine.util; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.data.RelationsQuery; @@ -39,10 +39,10 @@ public class EntitiesRelatedEntityIdAsyncLoader { ListenableFuture> asyncRelation = relationService.findByQuery(ctx.getTenantId(), query); if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) - : Futures.immediateFuture(null)); + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { return Futures.transformAsync(asyncRelation, r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) - : Futures.immediateFuture(null)); + : Futures.immediateFuture(null), MoreExecutors.directExecutor()); } return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java index 1a2ff9a1c1..3ff25e1e8b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java @@ -15,14 +15,20 @@ */ package org.thingsboard.rule.engine.util; -import com.google.common.util.concurrent.AsyncFunction; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.alarm.AlarmId; -import org.thingsboard.server.common.data.id.*; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; public class EntitiesTenantIdAsyncLoader { @@ -51,6 +57,7 @@ public class EntitiesTenantIdAsyncLoader { private static ListenableFuture getTenantAsync(ListenableFuture future) { return Futures.transformAsync(future, in -> { return in != null ? Futures.immediateFuture(in.getTenantId()) - : Futures.immediateFuture(null);}); + : Futures.immediateFuture(null); + }, MoreExecutors.directExecutor()); } } From aabc22d7d2101d0a89908c02eb6e169a9066a02f Mon Sep 17 00:00:00 2001 From: VoBa Date: Tue, 10 Mar 2020 16:52:50 +0200 Subject: [PATCH 254/261] Non root docker user (#2460) * Non root docker user * Fixes for user - signle user for all services * Base image changed * Fixes for pvc removal * Moved to be in sync with PE * Changed to TB repository --- .../src/main/scripts/control/deb/postinst | 4 ++-- .../src/main/scripts/control/deb/preinst | 10 ++++---- .../src/main/scripts/control/rpm/postinst | 4 ++-- .../main/scripts/control/thingsboard.service | 2 +- .../src/main/scripts/install/install.sh | 2 +- .../src/main/scripts/install/upgrade.sh | 2 +- docker/README.md | 7 ++++++ docker/docker-create-log-folders.sh | 24 +++++++++++++++++++ k8s/database-setup.yml | 2 +- k8s/k8s-delete-all.sh | 4 +++- k8s/k8s-install-tb.sh | 2 +- k8s/k8s-upgrade-tb.sh | 2 +- k8s/postgres.yml | 2 ++ msa/js-executor/docker/Dockerfile | 4 +++- msa/js-executor/docker/start-js-executor.sh | 4 +++- msa/js-executor/pom.xml | 1 - msa/tb-node/docker/Dockerfile | 4 ++++ msa/tb-node/docker/start-tb-node.sh | 4 +++- msa/tb-node/pom.xml | 1 - msa/tb/pom.xml | 1 - msa/transport/coap/docker/Dockerfile | 2 ++ .../coap/docker/start-tb-coap-transport.sh | 2 ++ msa/transport/coap/pom.xml | 1 - msa/transport/http/docker/Dockerfile | 2 ++ .../http/docker/start-tb-http-transport.sh | 2 ++ msa/transport/http/pom.xml | 1 - msa/transport/mqtt/docker/Dockerfile | 2 ++ .../mqtt/docker/start-tb-mqtt-transport.sh | 2 ++ msa/transport/mqtt/pom.xml | 1 - msa/web-ui/docker/Dockerfile | 4 +++- msa/web-ui/docker/start-web-ui.sh | 4 +++- msa/web-ui/pom.xml | 1 - pom.xml | 1 + .../src/main/scripts/control/deb/postinst | 4 ++-- .../coap/src/main/scripts/control/deb/preinst | 10 ++++---- .../src/main/scripts/control/rpm/postinst | 4 ++-- .../scripts/control/tb-coap-transport.service | 2 +- .../src/main/scripts/control/deb/postinst | 4 ++-- .../http/src/main/scripts/control/deb/preinst | 10 ++++---- .../src/main/scripts/control/rpm/postinst | 4 ++-- .../scripts/control/tb-http-transport.service | 2 +- .../src/main/scripts/control/deb/postinst | 4 ++-- .../mqtt/src/main/scripts/control/deb/preinst | 10 ++++---- .../src/main/scripts/control/rpm/postinst | 4 ++-- .../scripts/control/tb-mqtt-transport.service | 2 +- 45 files changed, 113 insertions(+), 58 deletions(-) create mode 100755 docker/docker-create-log-folders.sh diff --git a/application/src/main/scripts/control/deb/postinst b/application/src/main/scripts/control/deb/postinst index 00979d1b1c..b59dff9252 100644 --- a/application/src/main/scripts/control/deb/postinst +++ b/application/src/main/scripts/control/deb/postinst @@ -2,8 +2,8 @@ set -e -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || : exit 0 diff --git a/application/src/main/scripts/control/deb/preinst b/application/src/main/scripts/control/deb/preinst index ba4f417beb..eebe378588 100644 --- a/application/src/main/scripts/control/deb/preinst +++ b/application/src/main/scripts/control/deb/preinst @@ -2,21 +2,21 @@ set -e -if ! getent group ${pkg.name} >/dev/null; then - addgroup --system ${pkg.name} +if ! getent group ${pkg.user} >/dev/null; then + addgroup --system ${pkg.user} fi -if ! getent passwd ${pkg.name} >/dev/null; then +if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ - --ingroup ${pkg.name} \ + --ingroup ${pkg.user} \ --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ --no-create-home \ -gecos "Thingsboard application" \ - ${pkg.name} + ${pkg.user} fi exit 0 \ No newline at end of file diff --git a/application/src/main/scripts/control/rpm/postinst b/application/src/main/scripts/control/rpm/postinst index 8a7a88f7e0..d8021e2dd9 100644 --- a/application/src/main/scripts/control/rpm/postinst +++ b/application/src/main/scripts/control/rpm/postinst @@ -1,7 +1,7 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} if [ $1 -eq 1 ] ; then # Initial installation diff --git a/application/src/main/scripts/control/thingsboard.service b/application/src/main/scripts/control/thingsboard.service index d456fc03c0..3fee5c88df 100644 --- a/application/src/main/scripts/control/thingsboard.service +++ b/application/src/main/scripts/control/thingsboard.service @@ -3,7 +3,7 @@ Description=${pkg.name} After=syslog.target [Service] -User=${pkg.name} +User=${pkg.user} ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar SuccessExitStatus=143 diff --git a/application/src/main/scripts/install/install.sh b/application/src/main/scripts/install/install.sh index eb6025a261..acea08efde 100755 --- a/application/src/main/scripts/install/install.sh +++ b/application/src/main/scripts/install/install.sh @@ -44,7 +44,7 @@ installDir=${pkg.installFolder}/data source "${CONF_FOLDER}/${configfile}" -run_user=${pkg.name} +run_user=${pkg.user} su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ -Dinstall.data_dir=${installDir} \ diff --git a/application/src/main/scripts/install/upgrade.sh b/application/src/main/scripts/install/upgrade.sh index d4a49f8094..068276f2cb 100755 --- a/application/src/main/scripts/install/upgrade.sh +++ b/application/src/main/scripts/install/upgrade.sh @@ -43,7 +43,7 @@ installDir=${pkg.installFolder}/data source "${CONF_FOLDER}/${configfile}" -run_user=${pkg.name} +run_user=${pkg.user} su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \ -Dinstall.data_dir=${installDir} \ diff --git a/docker/README.md b/docker/README.md index d4655f8863..ff61c2599b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -17,6 +17,13 @@ In order to set database type change the value of `DATABASE` variable in `.env` **NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.cassandra.yml` for details). +Execute the following command to create log folders for the services and chown of these folders to the docker container users. +To be able to change user, **chown** command is used, which requires sudo permissions (script will request password for a sudo access): + +` +$ ./docker-create-log-folders.sh +` + Execute the following command to run installation: ` diff --git a/docker/docker-create-log-folders.sh b/docker/docker-create-log-folders.sh new file mode 100755 index 0000000000..1ac4539b30 --- /dev/null +++ b/docker/docker-create-log-folders.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# +# Copyright © 2016-2020 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. +# + +mkdir -p tb-node/log/ && sudo chown -R 799:799 tb-node/log/ + +mkdir -p tb-transports/coap/log && sudo chown -R 799:799 tb-transports/coap/log + +mkdir -p tb-transports/http/log && sudo chown -R 799:799 tb-transports/http/log + +mkdir -p tb-transports/mqtt/log && sudo chown -R 799:799 tb-transports/mqtt/log \ No newline at end of file diff --git a/k8s/database-setup.yml b/k8s/database-setup.yml index b48d6baf2b..d6f08d89bd 100644 --- a/k8s/database-setup.yml +++ b/k8s/database-setup.yml @@ -39,5 +39,5 @@ spec: volumeMounts: - mountPath: /config name: tb-node-config - command: ['sh', '-c', 'while [ ! -f /install-finished ]; do sleep 2; done;'] + command: ['sh', '-c', 'while [ ! -f /tmp/install-finished ]; do sleep 2; done;'] restartPolicy: Never diff --git a/k8s/k8s-delete-all.sh b/k8s/k8s-delete-all.sh index b0373b42f3..c5f531532c 100755 --- a/k8s/k8s-delete-all.sh +++ b/k8s/k8s-delete-all.sh @@ -15,4 +15,6 @@ # limitations under the License. # -kubectl -n thingsboard delete svc,sts,deploy,pv,pvc,cm,po,ing --all +kubectl -n thingsboard delete svc,sts,deploy,cm,po,ing --all + +kubectl -n thingsboard get pvc --no-headers=true | awk '//{print $1}' | xargs kubectl -n thingsboard delete --ignore-not-found=true pvc \ No newline at end of file diff --git a/k8s/k8s-install-tb.sh b/k8s/k8s-install-tb.sh index c13c8176ee..1702a5b3b5 100755 --- a/k8s/k8s-install-tb.sh +++ b/k8s/k8s-install-tb.sh @@ -22,7 +22,7 @@ function installTb() { kubectl apply -f tb-node-configmap.yml kubectl apply -f database-setup.yml && kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s && - kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /install-finished;' + kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /tmp/install-finished;' kubectl delete pod tb-db-setup diff --git a/k8s/k8s-upgrade-tb.sh b/k8s/k8s-upgrade-tb.sh index a7d94174d4..a97db5ea97 100755 --- a/k8s/k8s-upgrade-tb.sh +++ b/k8s/k8s-upgrade-tb.sh @@ -38,6 +38,6 @@ fi kubectl apply -f database-setup.yml && kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s && -kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /install-finished;' +kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /tmp/install-finished;' kubectl delete pod tb-db-setup diff --git a/k8s/postgres.yml b/k8s/postgres.yml index 56679ff880..08c7fe8d66 100644 --- a/k8s/postgres.yml +++ b/k8s/postgres.yml @@ -58,6 +58,8 @@ spec: env: - name: POSTGRES_DB value: "thingsboard" + - name: POSTGRES_PASSWORD + value: "postgres" - name: PGDATA value: /var/lib/postgresql/data/pgdata volumeMounts: diff --git a/msa/js-executor/docker/Dockerfile b/msa/js-executor/docker/Dockerfile index 4f4c85be82..276fd03b13 100644 --- a/msa/js-executor/docker/Dockerfile +++ b/msa/js-executor/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM debian:stretch +FROM thingsboard/base COPY start-js-executor.sh ${pkg.name}.deb /tmp/ @@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN update-rc.d ${pkg.name} disable +USER ${pkg.user} + CMD ["start-js-executor.sh"] diff --git a/msa/js-executor/docker/start-js-executor.sh b/msa/js-executor/docker/start-js-executor.sh index 5415d04964..e3f6f85fab 100755 --- a/msa/js-executor/docker/start-js-executor.sh +++ b/msa/js-executor/docker/start-js-executor.sh @@ -26,4 +26,6 @@ identity=${pkg.name} source "${CONF_FOLDER}/${configfile}" -su -s /bin/sh -c "$mainfile" +cd ${pkg.installFolder}/bin + +exec /bin/sh -c "$mainfile" diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index f85355a11e..bac7fb35e9 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../.. tb-js-executor tb-js-executor - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} ${project.build.directory}/package/linux diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index 4cc9838a96..eee8330f15 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -25,4 +25,8 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : +RUN chown -R ${pkg.user}:${pkg.user} /tmp + +USER ${pkg.user} + CMD ["start-tb-node.sh"] diff --git a/msa/tb-node/docker/start-tb-node.sh b/msa/tb-node/docker/start-tb-node.sh index 9b20fdca90..dca56164e9 100755 --- a/msa/tb-node/docker/start-tb-node.sh +++ b/msa/tb-node/docker/start-tb-node.sh @@ -18,12 +18,14 @@ CONF_FOLDER="/config" jarfile=${pkg.installFolder}/bin/${pkg.name}.jar configfile=${pkg.name}.conf -run_user=${pkg.name} +run_user=${pkg.user} source "${CONF_FOLDER}/${configfile}" export LOADER_PATH=/config,${LOADER_PATH} +cd ${pkg.installFolder}/bin + if [ "$INSTALL_TB" == "true" ]; then if [ "$LOAD_DEMO" == "true" ]; then diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 6687a6cc06..6502aed0d6 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../.. thingsboard tb-node - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index 1afecd234a..03d750251a 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -38,7 +38,6 @@ tb tb-postgres tb-cassandra - thingsboard /usr/share/${pkg.name} 2.4.2 diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile index 5c5dddef50..07cb0101b9 100644 --- a/msa/transport/coap/docker/Dockerfile +++ b/msa/transport/coap/docker/Dockerfile @@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN update-rc.d ${pkg.name} disable +USER ${pkg.user} + CMD ["start-tb-coap-transport.sh"] diff --git a/msa/transport/coap/docker/start-tb-coap-transport.sh b/msa/transport/coap/docker/start-tb-coap-transport.sh index c96368ce23..23ab476734 100755 --- a/msa/transport/coap/docker/start-tb-coap-transport.sh +++ b/msa/transport/coap/docker/start-tb-coap-transport.sh @@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH} echo "Starting '${project.name}' ..." +cd ${pkg.installFolder}/bin + exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ -Dlogging.config=/config/logback.xml \ diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 716e8f2f07..b4645f7253 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../../.. tb-coap-transport tb-coap-transport - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile index 13e8075549..b49cf204f8 100644 --- a/msa/transport/http/docker/Dockerfile +++ b/msa/transport/http/docker/Dockerfile @@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN update-rc.d ${pkg.name} disable +USER ${pkg.user} + CMD ["start-tb-http-transport.sh"] diff --git a/msa/transport/http/docker/start-tb-http-transport.sh b/msa/transport/http/docker/start-tb-http-transport.sh index 600d538a91..eb15edf482 100755 --- a/msa/transport/http/docker/start-tb-http-transport.sh +++ b/msa/transport/http/docker/start-tb-http-transport.sh @@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH} echo "Starting '${project.name}' ..." +cd ${pkg.installFolder}/bin + exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ -Dlogging.config=/config/logback.xml \ diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 5d12dec0ed..f91053a391 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../../.. tb-http-transport tb-http-transport - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile index 100f65951d..149911f8b5 100644 --- a/msa/transport/mqtt/docker/Dockerfile +++ b/msa/transport/mqtt/docker/Dockerfile @@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN update-rc.d ${pkg.name} disable +USER ${pkg.user} + CMD ["start-tb-mqtt-transport.sh"] diff --git a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh index 214599e138..2556d93b1d 100755 --- a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh +++ b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh @@ -25,6 +25,8 @@ export LOADER_PATH=/config,${LOADER_PATH} echo "Starting '${project.name}' ..." +cd ${pkg.installFolder}/bin + exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ -Dlogging.config=/config/logback.xml \ diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index 25c1b0045e..b7ebccc4be 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../../.. tb-mqtt-transport tb-mqtt-transport - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} diff --git a/msa/web-ui/docker/Dockerfile b/msa/web-ui/docker/Dockerfile index 8f5e5a0498..3609c289e4 100644 --- a/msa/web-ui/docker/Dockerfile +++ b/msa/web-ui/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM debian:stretch +FROM thingsboard/base COPY start-web-ui.sh ${pkg.name}.deb /tmp/ @@ -25,4 +25,6 @@ RUN dpkg -i /tmp/${pkg.name}.deb RUN update-rc.d ${pkg.name} disable +USER ${pkg.user} + CMD ["start-web-ui.sh"] diff --git a/msa/web-ui/docker/start-web-ui.sh b/msa/web-ui/docker/start-web-ui.sh index 5415d04964..e3f6f85fab 100755 --- a/msa/web-ui/docker/start-web-ui.sh +++ b/msa/web-ui/docker/start-web-ui.sh @@ -26,4 +26,6 @@ identity=${pkg.name} source "${CONF_FOLDER}/${configfile}" -su -s /bin/sh -c "$mainfile" +cd ${pkg.installFolder}/bin + +exec /bin/sh -c "$mainfile" diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index ad750f6706..eff6772de4 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -36,7 +36,6 @@ ${basedir}/../.. tb-web-ui tb-web-ui - thingsboard /var/log/${pkg.name} /usr/share/${pkg.name} ${project.build.directory}/package/linux diff --git a/pom.xml b/pom.xml index ab7e658abf..6725766840 100755 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,7 @@ ${basedir} + thingsboard 2.1.3.RELEASE 5.1.5.RELEASE 5.1.4.RELEASE diff --git a/transport/coap/src/main/scripts/control/deb/postinst b/transport/coap/src/main/scripts/control/deb/postinst index d4066c027b..0767d3f2c7 100644 --- a/transport/coap/src/main/scripts/control/deb/postinst +++ b/transport/coap/src/main/scripts/control/deb/postinst @@ -1,6 +1,6 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} update-rc.d ${pkg.name} defaults diff --git a/transport/coap/src/main/scripts/control/deb/preinst b/transport/coap/src/main/scripts/control/deb/preinst index 6be5959285..d2ebea46d7 100644 --- a/transport/coap/src/main/scripts/control/deb/preinst +++ b/transport/coap/src/main/scripts/control/deb/preinst @@ -1,18 +1,18 @@ #!/bin/sh -if ! getent group ${pkg.name} >/dev/null; then - addgroup --system ${pkg.name} +if ! getent group ${pkg.user} >/dev/null; then + addgroup --system ${pkg.user} fi -if ! getent passwd ${pkg.name} >/dev/null; then +if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ - --ingroup ${pkg.name} \ + --ingroup ${pkg.user} \ --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ --no-create-home \ -gecos "Thingsboard application" \ - ${pkg.name} + ${pkg.user} fi diff --git a/transport/coap/src/main/scripts/control/rpm/postinst b/transport/coap/src/main/scripts/control/rpm/postinst index 8a7a88f7e0..d8021e2dd9 100644 --- a/transport/coap/src/main/scripts/control/rpm/postinst +++ b/transport/coap/src/main/scripts/control/rpm/postinst @@ -1,7 +1,7 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} if [ $1 -eq 1 ] ; then # Initial installation diff --git a/transport/coap/src/main/scripts/control/tb-coap-transport.service b/transport/coap/src/main/scripts/control/tb-coap-transport.service index d456fc03c0..3fee5c88df 100644 --- a/transport/coap/src/main/scripts/control/tb-coap-transport.service +++ b/transport/coap/src/main/scripts/control/tb-coap-transport.service @@ -3,7 +3,7 @@ Description=${pkg.name} After=syslog.target [Service] -User=${pkg.name} +User=${pkg.user} ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar SuccessExitStatus=143 diff --git a/transport/http/src/main/scripts/control/deb/postinst b/transport/http/src/main/scripts/control/deb/postinst index d4066c027b..0767d3f2c7 100644 --- a/transport/http/src/main/scripts/control/deb/postinst +++ b/transport/http/src/main/scripts/control/deb/postinst @@ -1,6 +1,6 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} update-rc.d ${pkg.name} defaults diff --git a/transport/http/src/main/scripts/control/deb/preinst b/transport/http/src/main/scripts/control/deb/preinst index 6be5959285..d2ebea46d7 100644 --- a/transport/http/src/main/scripts/control/deb/preinst +++ b/transport/http/src/main/scripts/control/deb/preinst @@ -1,18 +1,18 @@ #!/bin/sh -if ! getent group ${pkg.name} >/dev/null; then - addgroup --system ${pkg.name} +if ! getent group ${pkg.user} >/dev/null; then + addgroup --system ${pkg.user} fi -if ! getent passwd ${pkg.name} >/dev/null; then +if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ - --ingroup ${pkg.name} \ + --ingroup ${pkg.user} \ --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ --no-create-home \ -gecos "Thingsboard application" \ - ${pkg.name} + ${pkg.user} fi diff --git a/transport/http/src/main/scripts/control/rpm/postinst b/transport/http/src/main/scripts/control/rpm/postinst index 8a7a88f7e0..d8021e2dd9 100644 --- a/transport/http/src/main/scripts/control/rpm/postinst +++ b/transport/http/src/main/scripts/control/rpm/postinst @@ -1,7 +1,7 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} if [ $1 -eq 1 ] ; then # Initial installation diff --git a/transport/http/src/main/scripts/control/tb-http-transport.service b/transport/http/src/main/scripts/control/tb-http-transport.service index d456fc03c0..3fee5c88df 100644 --- a/transport/http/src/main/scripts/control/tb-http-transport.service +++ b/transport/http/src/main/scripts/control/tb-http-transport.service @@ -3,7 +3,7 @@ Description=${pkg.name} After=syslog.target [Service] -User=${pkg.name} +User=${pkg.user} ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar SuccessExitStatus=143 diff --git a/transport/mqtt/src/main/scripts/control/deb/postinst b/transport/mqtt/src/main/scripts/control/deb/postinst index d4066c027b..0767d3f2c7 100644 --- a/transport/mqtt/src/main/scripts/control/deb/postinst +++ b/transport/mqtt/src/main/scripts/control/deb/postinst @@ -1,6 +1,6 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} update-rc.d ${pkg.name} defaults diff --git a/transport/mqtt/src/main/scripts/control/deb/preinst b/transport/mqtt/src/main/scripts/control/deb/preinst index 6be5959285..d2ebea46d7 100644 --- a/transport/mqtt/src/main/scripts/control/deb/preinst +++ b/transport/mqtt/src/main/scripts/control/deb/preinst @@ -1,18 +1,18 @@ #!/bin/sh -if ! getent group ${pkg.name} >/dev/null; then - addgroup --system ${pkg.name} +if ! getent group ${pkg.user} >/dev/null; then + addgroup --system ${pkg.user} fi -if ! getent passwd ${pkg.name} >/dev/null; then +if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ - --ingroup ${pkg.name} \ + --ingroup ${pkg.user} \ --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ --no-create-home \ -gecos "Thingsboard application" \ - ${pkg.name} + ${pkg.user} fi diff --git a/transport/mqtt/src/main/scripts/control/rpm/postinst b/transport/mqtt/src/main/scripts/control/rpm/postinst index 8a7a88f7e0..d8021e2dd9 100644 --- a/transport/mqtt/src/main/scripts/control/rpm/postinst +++ b/transport/mqtt/src/main/scripts/control/rpm/postinst @@ -1,7 +1,7 @@ #!/bin/sh -chown -R ${pkg.name}: ${pkg.logFolder} -chown -R ${pkg.name}: ${pkg.installFolder} +chown -R ${pkg.user}: ${pkg.logFolder} +chown -R ${pkg.user}: ${pkg.installFolder} if [ $1 -eq 1 ] ; then # Initial installation diff --git a/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service index d456fc03c0..3fee5c88df 100644 --- a/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service +++ b/transport/mqtt/src/main/scripts/control/tb-mqtt-transport.service @@ -3,7 +3,7 @@ Description=${pkg.name} After=syslog.target [Service] -User=${pkg.name} +User=${pkg.user} ExecStart=${pkg.installFolder}/bin/${pkg.name}.jar SuccessExitStatus=143 From 188c3e5b636e981cc3534c74bd27fdaaf6173fcd Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 10 Mar 2020 17:49:00 +0200 Subject: [PATCH 255/261] Upgrade Sql Ts & Timescale improvements (#2495) * psql & timescale ts upgrade improved * fix typo * fix typo 2 * removed tenant_id from timescale db schema & upgade scipt logic --- .../upgrade/2.4.3/schema_update_psql_ts.sql | 92 ++++++------- .../2.4.3/schema_update_timescale_ts.sql | 125 ++++++++---------- .../AbstractSqlTsDatabaseUpgradeService.java | 73 +++------- .../install/PsqlTsDatabaseUpgradeService.java | 51 ++++--- .../TimescaleTsDatabaseSchemaService.java | 4 +- .../TimescaleTsDatabaseUpgradeService.java | 54 ++++---- .../dao/model/sql/AbstractTsKvEntity.java | 5 + .../model/sqlts/latest/TsKvLatestEntity.java | 4 - .../ts/TimescaleTsKvCompositeKey.java | 1 - .../timescale/ts/TimescaleTsKvEntity.java | 19 +-- .../server/dao/model/sqlts/ts/TsKvEntity.java | 4 - ...stractChunkedAggregationTimeseriesDao.java | 36 ++--- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 18 +-- .../TimescaleInsertTsRepository.java | 43 +++--- .../timescale/AggregationRepository.java | 25 ++-- .../timescale/TimescaleTimeseriesDao.java | 45 +++---- .../timescale/TsKvTimescaleRepository.java | 10 +- .../resources/sql/schema-timescale-idx.sql | 17 --- .../main/resources/sql/schema-timescale.sql | 5 +- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../sql/timescale/drop-all-tables.sql | 2 +- 21 files changed, 259 insertions(+), 376 deletions(-) delete mode 100644 dao/src/main/resources/sql/schema-timescale-idx.sql diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql index 2d012336ab..3d17bbef2f 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql @@ -14,33 +14,27 @@ -- limitations under the License. -- --- select check_version(); +-- call check_version(); -CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ +CREATE OR REPLACE PROCEDURE check_version(INOUT valid_version boolean) LANGUAGE plpgsql AS $BODY$ DECLARE current_version integer; - valid_version boolean; BEGIN RAISE NOTICE 'Check the current installed PostgreSQL version...'; SELECT current_setting('server_version_num') INTO current_version; - IF current_version < 100000 THEN - valid_version := FALSE; - ELSE - valid_version := TRUE; - END IF; - IF valid_version = FALSE THEN - RAISE NOTICE 'Postgres version should be at least more than 10!'; - ELSE + IF current_version > 110000 THEN RAISE NOTICE 'PostgreSQL version is valid!'; RAISE NOTICE 'Schema update started...'; + SELECT true INTO valid_version; + ELSE + RAISE NOTICE 'Postgres version should be at least more than 10!'; END IF; - RETURN valid_version; END; -$$ LANGUAGE 'plpgsql'; +$BODY$; --- select create_partition_ts_kv_table(); +-- call create_partition_ts_kv_table(); -CREATE OR REPLACE FUNCTION create_partition_ts_kv_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$ BEGIN ALTER TABLE ts_kv @@ -57,11 +51,11 @@ BEGIN ALTER TABLE ts_kv ALTER COLUMN key TYPE integer USING key::integer; END; -$$ LANGUAGE 'plpgsql'; +$$; --- select create_new_ts_kv_latest_table(); +-- call create_new_ts_kv_latest_table(); -CREATE OR REPLACE FUNCTION create_new_ts_kv_latest_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table() LANGUAGE plpgsql AS $$ BEGIN ALTER TABLE ts_kv_latest @@ -81,13 +75,13 @@ BEGIN ALTER TABLE ts_kv_latest ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key); END; -$$ LANGUAGE 'plpgsql'; +$$; --- select create_partitions(); +-- call create_partitions(); + +CREATE OR REPLACE PROCEDURE create_partitions() LANGUAGE plpgsql AS $$ -CREATE OR REPLACE FUNCTION create_partitions() RETURNS VOID AS -$$ DECLARE partition_date varchar; from_ts bigint; @@ -111,11 +105,11 @@ BEGIN CLOSE key_cursor; END; -$$ language 'plpgsql'; +$$; --- select create_ts_kv_dictionary_table(); +-- call create_ts_kv_dictionary_table(); -CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$ BEGIN CREATE TABLE IF NOT EXISTS ts_kv_dictionary @@ -125,12 +119,12 @@ BEGIN CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); END; -$$ LANGUAGE 'plpgsql'; +$$; + +-- call insert_into_dictionary(); --- select insert_into_dictionary(); +CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$ -CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS -$$ DECLARE insert_record RECORD; key_cursor CURSOR FOR SELECT DISTINCT key @@ -150,28 +144,27 @@ BEGIN END LOOP; CLOSE key_cursor; END; -$$ language 'plpgsql'; +$$; --- select insert_into_ts_kv(); +-- call insert_into_ts_kv(); -CREATE OR REPLACE FUNCTION insert_into_ts_kv() RETURNS void AS -$$ +CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid AS entity_id, + insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id, ts_kv_records.key AS key, ts_kv_records.ts AS ts, ts_kv_records.bool_v AS bool_v, ts_kv_records.str_v AS str_v, ts_kv_records.long_v AS long_v, ts_kv_records.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first_part_uuid, - SUBSTRING(entity_id, 4, 4) AS second_part_uuid, - SUBSTRING(entity_id, 1, 3) AS third_part_uuid, - SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid, - SUBSTRING(entity_id, 20) AS fifth_part_uuid, + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, + SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, + SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part, + SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part, + SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part, key_id AS key, ts, bool_v, @@ -198,28 +191,27 @@ BEGIN END LOOP; CLOSE insert_cursor; END; -$$ LANGUAGE 'plpgsql'; +$$; --- select insert_into_ts_kv_latest(); +-- call insert_into_ts_kv_latest(); -CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS -$$ +CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(first_part_uuid, '-', second_part_uuid, '-1', third_part_uuid, '-', fourth_part_uuid, '-', fifth_part_uuid)::uuid AS entity_id, + insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id, ts_kv_latest_records.key AS key, ts_kv_latest_records.ts AS ts, ts_kv_latest_records.bool_v AS bool_v, ts_kv_latest_records.str_v AS str_v, ts_kv_latest_records.long_v AS long_v, ts_kv_latest_records.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(entity_id, 8, 8) AS first_part_uuid, - SUBSTRING(entity_id, 4, 4) AS second_part_uuid, - SUBSTRING(entity_id, 1, 3) AS third_part_uuid, - SUBSTRING(entity_id, 16, 4) AS fourth_part_uuid, - SUBSTRING(entity_id, 20) AS fifth_part_uuid, + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, + SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, + SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part, + SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part, + SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part, key_id AS key, ts, bool_v, @@ -246,6 +238,6 @@ BEGIN END LOOP; CLOSE insert_cursor; END; -$$ LANGUAGE 'plpgsql'; +$$; diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql index b8a3f1850e..ebbc6933ae 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql @@ -14,60 +14,51 @@ -- limitations under the License. -- --- select check_version(); +-- call check_version(); + +CREATE OR REPLACE PROCEDURE check_version(INOUT valid_version boolean) LANGUAGE plpgsql AS $BODY$ -CREATE OR REPLACE FUNCTION check_version() RETURNS boolean AS $$ DECLARE current_version integer; - valid_version boolean; BEGIN RAISE NOTICE 'Check the current installed PostgreSQL version...'; SELECT current_setting('server_version_num') INTO current_version; - IF current_version < 90600 THEN - valid_version := FALSE; - ELSE - valid_version := TRUE; - END IF; - IF valid_version = FALSE THEN - RAISE NOTICE 'Postgres version should be at least more than 9.6!'; - ELSE + IF current_version > 110000 THEN RAISE NOTICE 'PostgreSQL version is valid!'; RAISE NOTICE 'Schema update started...'; + SELECT true INTO valid_version; + ELSE + RAISE NOTICE 'Postgres version should be at least more than 10!'; END IF; - RETURN valid_version; END; -$$ LANGUAGE 'plpgsql'; +$BODY$; --- select create_new_tenant_ts_kv_table(); +-- call create_new_ts_kv_table(); -CREATE OR REPLACE FUNCTION create_new_tenant_ts_kv_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_new_ts_kv_table() LANGUAGE plpgsql AS $$ BEGIN ALTER TABLE tenant_ts_kv RENAME TO tenant_ts_kv_old; - CREATE TABLE IF NOT EXISTS tenant_ts_kv + CREATE TABLE IF NOT EXISTS ts_kv ( LIKE tenant_ts_kv_old ); - ALTER TABLE tenant_ts_kv - ALTER COLUMN tenant_id TYPE uuid USING tenant_id::uuid; - ALTER TABLE tenant_ts_kv - ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; - ALTER TABLE tenant_ts_kv - ALTER COLUMN key TYPE integer USING key::integer; - ALTER TABLE tenant_ts_kv - ADD CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY(tenant_id, entity_id, key, ts); + ALTER TABLE ts_kv ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; + ALTER TABLE ts_kv ALTER COLUMN key TYPE integer USING key::integer; + ALTER INDEX ts_kv_pkey RENAME TO tenant_ts_kv_pkey_old; ALTER INDEX idx_tenant_ts_kv RENAME TO idx_tenant_ts_kv_old; ALTER INDEX tenant_ts_kv_ts_idx RENAME TO tenant_ts_kv_ts_idx_old; --- PERFORM create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => 86400000, if_not_exists => true); - CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts); + ALTER TABLE ts_kv ADD CONSTRAINT ts_kv_pkey PRIMARY KEY(entity_id, key, ts); +-- CREATE INDEX IF NOT EXISTS ts_kv_ts_idx ON ts_kv(ts DESC); + ALTER TABLE ts_kv DROP COLUMN IF EXISTS tenant_id; END; -$$ LANGUAGE 'plpgsql'; +$$; --- select create_ts_kv_latest_table(); +-- call create_ts_kv_latest_table(); -CREATE OR REPLACE FUNCTION create_ts_kv_latest_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_ts_kv_latest_table() LANGUAGE plpgsql AS $$ BEGIN CREATE TABLE IF NOT EXISTS ts_kv_latest @@ -82,12 +73,12 @@ BEGIN CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) ); END; -$$ LANGUAGE 'plpgsql'; +$$; --- select create_ts_kv_dictionary_table(); +-- call create_ts_kv_dictionary_table(); -CREATE OR REPLACE FUNCTION create_ts_kv_dictionary_table() RETURNS VOID AS $$ +CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$ BEGIN CREATE TABLE IF NOT EXISTS ts_kv_dictionary @@ -97,12 +88,12 @@ BEGIN CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); END; -$$ LANGUAGE 'plpgsql'; +$$; --- select insert_into_dictionary(); +-- call insert_into_dictionary(); + +CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$ -CREATE OR REPLACE FUNCTION insert_into_dictionary() RETURNS VOID AS -$$ DECLARE insert_record RECORD; key_cursor CURSOR FOR SELECT DISTINCT key @@ -122,34 +113,28 @@ BEGIN END LOOP; CLOSE key_cursor; END; -$$ language 'plpgsql'; +$$; + +-- call insert_into_ts_kv(); --- select insert_into_tenant_ts_kv(); +CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ -CREATE OR REPLACE FUNCTION insert_into_tenant_ts_kv() RETURNS void AS -$$ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; insert_record RECORD; - insert_cursor CURSOR FOR SELECT CONCAT(tenant_id_first_part_uuid, '-', tenant_id_second_part_uuid, '-1', tenant_id_third_part_uuid, '-', tenant_id_fourth_part_uuid, '-', tenant_id_fifth_part_uuid)::uuid AS tenant_id, - CONCAT(entity_id_first_part_uuid, '-', entity_id_second_part_uuid, '-1', entity_id_third_part_uuid, '-', entity_id_fourth_part_uuid, '-', entity_id_fifth_part_uuid)::uuid AS entity_id, - tenant_ts_kv_records.key AS key, - tenant_ts_kv_records.ts AS ts, - tenant_ts_kv_records.bool_v AS bool_v, - tenant_ts_kv_records.str_v AS str_v, - tenant_ts_kv_records.long_v AS long_v, - tenant_ts_kv_records.dbl_v AS dbl_v - FROM (SELECT SUBSTRING(tenant_id, 8, 8) AS tenant_id_first_part_uuid, - SUBSTRING(tenant_id, 4, 4) AS tenant_id_second_part_uuid, - SUBSTRING(tenant_id, 1, 3) AS tenant_id_third_part_uuid, - SUBSTRING(tenant_id, 16, 4) AS tenant_id_fourth_part_uuid, - SUBSTRING(tenant_id, 20) AS tenant_id_fifth_part_uuid, - SUBSTRING(entity_id, 8, 8) AS entity_id_first_part_uuid, - SUBSTRING(entity_id, 4, 4) AS entity_id_second_part_uuid, - SUBSTRING(entity_id, 1, 3) AS entity_id_third_part_uuid, - SUBSTRING(entity_id, 16, 4) AS entity_id_fourth_part_uuid, - SUBSTRING(entity_id, 20) AS entity_id_fifth_part_uuid, + insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id, + new_ts_kv_records.key AS key, + new_ts_kv_records.ts AS ts, + new_ts_kv_records.bool_v AS bool_v, + new_ts_kv_records.str_v AS str_v, + new_ts_kv_records.long_v AS long_v, + new_ts_kv_records.dbl_v AS dbl_v + FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, + SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, + SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part, + SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part, + SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part, key_id AS key, ts, bool_v, @@ -157,31 +142,31 @@ DECLARE long_v, dbl_v FROM tenant_ts_kv_old - INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS tenant_ts_kv_records; + INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records; BEGIN OPEN insert_cursor; LOOP insert_counter := insert_counter + 1; FETCH insert_cursor INTO insert_record; IF NOT FOUND THEN - RAISE NOTICE '% records have been inserted into the new tenant_ts_kv table!',insert_counter - 1; + RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1; EXIT; END IF; - INSERT INTO tenant_ts_kv(tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) - VALUES (insert_record.tenant_id, insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, + INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) + VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, insert_record.long_v, insert_record.dbl_v); IF MOD(insert_counter, insert_size) = 0 THEN - RAISE NOTICE '% records have been inserted into the new tenant_ts_kv table!',insert_counter; + RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter; END IF; END LOOP; CLOSE insert_cursor; END; -$$ LANGUAGE 'plpgsql'; +$$; + +-- call insert_into_ts_kv_latest(); --- select insert_into_ts_kv_latest(); +CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$ -CREATE OR REPLACE FUNCTION insert_into_ts_kv_latest() RETURNS void AS -$$ DECLARE insert_size CONSTANT integer := 10000; insert_counter integer DEFAULT 0; @@ -191,7 +176,7 @@ DECLARE latest_records.key AS key, latest_records.entity_id AS entity_id, latest_records.ts AS ts - FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM tenant_ts_kv GROUP BY key, entity_id) AS latest_records; + FROM (SELECT DISTINCT key AS key, entity_id AS entity_id, MAX(ts) AS ts FROM ts_kv GROUP BY key, entity_id) AS latest_records; BEGIN OPEN insert_cursor; LOOP @@ -201,7 +186,7 @@ BEGIN RAISE NOTICE '% records have been inserted into the ts_kv_latest table!',insert_counter - 1; EXIT; END IF; - SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM tenant_ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts; + SELECT entity_id AS entity_id, key AS key, ts AS ts, bool_v AS bool_v, str_v AS str_v, long_v AS long_v, dbl_v AS dbl_v INTO insert_record FROM ts_kv WHERE entity_id = latest_record.entity_id AND key = latest_record.key AND ts = latest_record.ts; INSERT INTO ts_kv_latest(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, insert_record.long_v, insert_record.dbl_v); IF MOD(insert_counter, insert_size) = 0 THEN @@ -210,4 +195,4 @@ BEGIN END LOOP; CLOSE insert_cursor; END; -$$ LANGUAGE 'plpgsql'; +$$; diff --git a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java index fe56ac129c..901f773515 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java @@ -22,38 +22,21 @@ import org.springframework.beans.factory.annotation.Value; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; -import java.sql.CallableStatement; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; -import java.sql.Types; +import java.sql.Statement; @Slf4j public abstract class AbstractSqlTsDatabaseUpgradeService { protected static final String CALL_REGEX = "call "; - protected static final String CHECK_VERSION = "check_version()"; + protected static final String CHECK_VERSION = "check_version(false)"; + protected static final String CHECK_VERSION_TO_DELETE = "check_version(INOUT valid_version boolean)"; protected static final String DROP_TABLE = "DROP TABLE "; - protected static final String DROP_FUNCTION_IF_EXISTS = "DROP FUNCTION IF EXISTS "; - - private static final String CALL_CHECK_VERSION = CALL_REGEX + CHECK_VERSION; - - - private static final String FUNCTION = "function: {}"; - private static final String DROP_STATEMENT = "drop statement: {}"; - private static final String QUERY = "query: {}"; - private static final String SUCCESSFULLY_EXECUTED = "Successfully executed "; - private static final String FAILED_TO_EXECUTE = "Failed to execute "; - private static final String FAILED_DUE_TO = " due to: {}"; - - protected static final String SUCCESSFULLY_EXECUTED_FUNCTION = SUCCESSFULLY_EXECUTED + FUNCTION; - protected static final String FAILED_TO_EXECUTE_FUNCTION_DUE_TO = FAILED_TO_EXECUTE + FUNCTION + FAILED_DUE_TO; - - protected static final String SUCCESSFULLY_EXECUTED_DROP_STATEMENT = SUCCESSFULLY_EXECUTED + DROP_STATEMENT; - protected static final String FAILED_TO_EXECUTE_DROP_STATEMENT = FAILED_TO_EXECUTE + DROP_STATEMENT + FAILED_DUE_TO; - - protected static final String SUCCESSFULLY_EXECUTED_QUERY = SUCCESSFULLY_EXECUTED + QUERY; - protected static final String FAILED_TO_EXECUTE_QUERY = FAILED_TO_EXECUTE + QUERY + FAILED_DUE_TO; + protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS "; + protected static final String DROP_PROCEDURE_CHECK_VERSION = DROP_PROCEDURE_IF_EXISTS + CHECK_VERSION_TO_DELETE; @Value("${spring.datasource.url}") protected String dbUrl; @@ -78,23 +61,22 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { log.info("Check the current PostgreSQL version..."); boolean versionValid = false; try { - CallableStatement callableStatement = conn.prepareCall("{? = " + CALL_CHECK_VERSION + " }"); - callableStatement.registerOutParameter(1, Types.BOOLEAN); - callableStatement.execute(); - versionValid = callableStatement.getBoolean(1); - callableStatement.close(); + Statement statement = conn.createStatement(); + ResultSet resultSet = statement.executeQuery(CALL_REGEX + CHECK_VERSION); + resultSet.next(); + versionValid = resultSet.getBoolean(1); + statement.close(); } catch (Exception e) { log.info("Failed to check current PostgreSQL version due to: {}", e.getMessage()); } return versionValid; } - protected void executeFunction(Connection conn, String query) { - log.info("{} ... ", query); + protected void executeQuery(Connection conn, String query) { try { - CallableStatement callableStatement = conn.prepareCall("{" + query + "}"); - callableStatement.execute(); - SQLWarning warnings = callableStatement.getWarnings(); + Statement statement = conn.createStatement(); + statement.execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script + SQLWarning warnings = statement.getWarnings(); if (warnings != null) { log.info("{}", warnings.getMessage()); SQLWarning nextWarning = warnings.getNextWarning(); @@ -103,31 +85,10 @@ public abstract class AbstractSqlTsDatabaseUpgradeService { nextWarning = nextWarning.getNextWarning(); } } - callableStatement.close(); - log.info(SUCCESSFULLY_EXECUTED_FUNCTION, query.replace(CALL_REGEX, "")); - Thread.sleep(2000); - } catch (Exception e) { - log.info(FAILED_TO_EXECUTE_FUNCTION_DUE_TO, query, e.getMessage()); - } - } - - protected void executeDropStatement(Connection conn, String query) { - try { - conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - log.info(SUCCESSFULLY_EXECUTED_DROP_STATEMENT, query); - Thread.sleep(5000); - } catch (InterruptedException | SQLException e) { - log.info(FAILED_TO_EXECUTE_DROP_STATEMENT, query, e.getMessage()); - } - } - - protected void executeQuery(Connection conn, String query) { - try { - conn.createStatement().execute(query); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script - log.info(SUCCESSFULLY_EXECUTED_QUERY, query); Thread.sleep(5000); + log.info("Successfully executed query: {}", query); } catch (InterruptedException | SQLException e) { - log.info(FAILED_TO_EXECUTE_QUERY, query, e.getMessage()); + log.info("Failed to execute query: {} due to: {}", query, e.getMessage()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java index eb951ed9ae..96f2c126a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java @@ -57,14 +57,13 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD; private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD; - private static final String DROP_FUNCTION_CHECK_VERSION = DROP_FUNCTION_IF_EXISTS + CHECK_VERSION; - private static final String DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE; - private static final String DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE; - private static final String DROP_FUNCTION_CREATE_PARTITIONS = DROP_FUNCTION_IF_EXISTS + CREATE_PARTITIONS; - private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY; - private static final String DROP_FUNCTION_INSERT_INTO_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV; - private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; + private static final String DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITION_TS_KV_TABLE; + private static final String DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_LATEST_TABLE; + private static final String DROP_PROCEDURE_CREATE_PARTITIONS = DROP_PROCEDURE_IF_EXISTS + CREATE_PARTITIONS; + private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; + private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; + private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -76,30 +75,30 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe loadSql(conn); boolean versionValid = checkVersion(conn); if (!versionValid) { - log.info("PostgreSQL version should be at least more than 10!"); + log.info("PostgreSQL version should be at least more than 11!"); log.info("Please upgrade your PostgreSQL and restart the script!"); } else { log.info("PostgreSQL version is valid!"); log.info("Updating schema ..."); - executeFunction(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); - executeFunction(conn, CALL_CREATE_PARTITIONS); - executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); - executeFunction(conn, CALL_INSERT_INTO_DICTIONARY); - executeFunction(conn, CALL_INSERT_INTO_TS_KV); - executeFunction(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); - executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); + executeQuery(conn, CALL_CREATE_PARTITIONS); + executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); + executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); + executeQuery(conn, CALL_INSERT_INTO_TS_KV); + executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); + executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); - executeDropStatement(conn, DROP_TABLE_TS_KV_OLD); - executeDropStatement(conn, DROP_TABLE_TS_KV_LATEST_OLD); + executeQuery(conn, DROP_TABLE_TS_KV_OLD); + executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); - executeDropStatement(conn, DROP_FUNCTION_CHECK_VERSION); - executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITION_TS_KV_TABLE); - executeDropStatement(conn, DROP_FUNCTION_CREATE_PARTITIONS); - executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV); - executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, DROP_PROCEDURE_CHECK_VERSION); + executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITION_TS_KV_TABLE); + executeQuery(conn, DROP_PROCEDURE_CREATE_PARTITIONS); + executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV); + executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java index 92a0a837fa..e8c542d956 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseSchemaService.java @@ -45,13 +45,13 @@ public class TimescaleTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaS private long chunkTimeInterval; public TimescaleTsDatabaseSchemaService() { - super("schema-timescale.sql", "schema-timescale-idx.sql"); + super("schema-timescale.sql", null); } @Override public void createDatabaseSchema() throws Exception { super.createDatabaseSchema(); - executeQuery("SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); + executeQuery("SELECT create_hypertable('ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); } private void executeQuery(String query) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java index a2a9611581..e438f965c8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java @@ -43,27 +43,27 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr private static final String TENANT_TS_KV_OLD_TABLE = "tenant_ts_kv_old;"; private static final String CREATE_TS_KV_LATEST_TABLE = "create_ts_kv_latest_table()"; - private static final String CREATE_NEW_TENANT_TS_KV_TABLE = "create_new_tenant_ts_kv_table()"; + private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; - private static final String INSERT_INTO_TENANT_TS_KV = "insert_into_tenant_ts_kv()"; + private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE; - private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TENANT_TS_KV_TABLE; + private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE; private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; - private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TENANT_TS_KV; + private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV; private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE; - private static final String DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_LATEST_TABLE; - private static final String DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_FUNCTION_IF_EXISTS + CREATE_NEW_TENANT_TS_KV_TABLE; - private static final String DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE = DROP_FUNCTION_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; - private static final String DROP_FUNCTION_INSERT_INTO_DICTIONARY = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_DICTIONARY; - private static final String DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TENANT_TS_KV; - private static final String DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST = DROP_FUNCTION_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; + private static final String DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_LATEST_TABLE; + private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE; + private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; + private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; + private static final String DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; + private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; @Autowired private InstallScripts installScripts; @@ -78,33 +78,31 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr loadSql(conn); boolean versionValid = checkVersion(conn); if (!versionValid) { - log.info("PostgreSQL version should be at least more than 9.6!"); + log.info("PostgreSQL version should be at least more than 11!"); log.info("Please upgrade your PostgreSQL and restart the script!"); } else { log.info("PostgreSQL version is valid!"); log.info("Updating schema ..."); - executeFunction(conn, CALL_CREATE_TS_KV_LATEST_TABLE); - executeFunction(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); + executeQuery(conn, CALL_CREATE_TS_KV_LATEST_TABLE); + executeQuery(conn, CALL_CREATE_NEW_TENANT_TS_KV_TABLE); - executeQuery(conn, "SELECT create_hypertable('tenant_ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); + executeQuery(conn, "SELECT create_hypertable('ts_kv', 'ts', chunk_time_interval => " + chunkTimeInterval + ", if_not_exists => true);"); - executeFunction(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); - executeFunction(conn, CALL_INSERT_INTO_DICTIONARY); - executeFunction(conn, CALL_INSERT_INTO_TS_KV); - executeFunction(conn, CALL_INSERT_INTO_TS_KV_LATEST); + executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); + executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); + executeQuery(conn, CALL_INSERT_INTO_TS_KV); + executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); - //executeQuery(conn, "SELECT set_chunk_time_interval('tenant_ts_kv', " + chunkTimeInterval +");"); + executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE); - executeDropStatement(conn, DROP_OLD_TENANT_TS_KV_TABLE); + executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_LATEST_TABLE); + executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY); + executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV); + executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); - executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_LATEST_TABLE); - executeDropStatement(conn, DROP_FUNCTION_CREATE_TENANT_TS_KV_TABLE_COPY); - executeDropStatement(conn, DROP_FUNCTION_CREATE_TS_KV_DICTIONARY_TABLE); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_DICTIONARY); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV); - executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); - - executeQuery(conn, "ALTER TABLE tenant_ts_kv ADD COLUMN json_v json;"); + executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;"); log.info("schema timeseries updated!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java index f0dd03b5ca..36d9421c32 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java @@ -36,6 +36,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLU import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_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; @@ -53,6 +54,10 @@ public abstract class AbstractTsKvEntity implements ToData { @Column(name = ENTITY_ID_COLUMN, columnDefinition = "uuid") protected UUID entityId; + @Id + @Column(name = KEY_COLUMN) + protected int key; + @Id @Column(name = TS_COLUMN) protected Long ts; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java index 01fe8322e3..e7de4afa67 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java @@ -69,10 +69,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; }) public final class TsKvLatestEntity extends AbstractTsKvEntity { - @Id - @Column(name = KEY_COLUMN) - private int key; - @Override public boolean isNotEmpty() { return strValue != null || longValue != null || doubleValue != null || booleanValue != null || jsonValue != null; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java index e7db0572ec..afcf9b1d51 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvCompositeKey.java @@ -31,7 +31,6 @@ public class TimescaleTsKvCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -4089175869616037523L; - private UUID tenantId; private UUID entityId; private int key; private long ts; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java index 76a95667a9..832a85d3e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/ts/TimescaleTsKvEntity.java @@ -18,25 +18,18 @@ package org.thingsboard.server.dao.model.sqlts.timescale.ts; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; -import javax.persistence.Column; import javax.persistence.ColumnResult; import javax.persistence.ConstructorResult; import javax.persistence.Entity; -import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.NamedNativeQueries; import javax.persistence.NamedNativeQuery; import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMappings; import javax.persistence.Table; -import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_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; @@ -52,7 +45,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F @Data @EqualsAndHashCode(callSuper = true) @Entity -@Table(name = "tenant_ts_kv") +@Table(name = "ts_kv") @IdClass(TimescaleTsKvCompositeKey.class) @SqlResultSetMappings({ @SqlResultSetMapping( @@ -116,15 +109,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F resultSetMapping = "timescaleCountMapping" ) }) -public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToData { - - @Id - @Column(name = TENANT_ID_COLUMN, columnDefinition = "uuid") - private UUID tenantId; - - @Id - @Column(name = KEY_COLUMN) - private int key; +public final class TimescaleTsKvEntity extends AbstractTsKvEntity { public TimescaleTsKvEntity() { } 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 6d01b62d25..3a14d0c957 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 @@ -32,10 +32,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; @IdClass(TsKvCompositeKey.class) public final class TsKvEntity extends AbstractTsKvEntity { - @Id - @Column(name = KEY_COLUMN) - private int key; - public TsKvEntity() { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 588f2ef0e4..c4ac4e9fd8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -96,7 +96,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq @Override public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return getRemoveLatestFuture(tenantId, entityId, query); + return getRemoveLatestFuture(entityId, query); } @Override @@ -125,9 +125,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + protected ListenableFuture> findAllAsync(EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(tenantId, entityId, query); + return findAllAsyncWithLimit(entityId, query); } else { long stepTs = query.getStartTs(); List>> futures = new ArrayList<>(); @@ -135,7 +135,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq long startTs = stepTs; long endTs = stepTs + query.getInterval(); long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); + futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); stepTs = endTs; } return getTskvEntriesFuture(Futures.allAsList(futures)); @@ -143,7 +143,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + protected ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { Integer keyId = getOrSaveKeyId(query.getKey()); List tsKvEntities = tsKvRepository.findAllWithLimit( entityId.getId(), @@ -157,9 +157,9 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); } - protected ListenableFuture> findAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + private ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { List> entitiesFutures = new ArrayList<>(); - switchAggregation(tenantId, entityId, key, startTs, endTs, aggregation, entitiesFutures); + switchAggregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); return Futures.transform(setFutures(entitiesFutures), entity -> { if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityId.getId()); @@ -172,29 +172,29 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq }, MoreExecutors.directExecutor()); } - protected void switchAggregation(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { + protected void switchAggregation(EntityId entityId, String key, long startTs, long endTs, Aggregation aggregation, List> entitiesFutures) { switch (aggregation) { case AVG: - findAvg(tenantId, entityId, key, startTs, endTs, entitiesFutures); + findAvg(entityId, key, startTs, endTs, entitiesFutures); break; case MAX: - findMax(tenantId, entityId, key, startTs, endTs, entitiesFutures); + findMax(entityId, key, startTs, endTs, entitiesFutures); break; case MIN: - findMin(tenantId, entityId, key, startTs, endTs, entitiesFutures); + findMin(entityId, key, startTs, endTs, entitiesFutures); break; case SUM: - findSum(tenantId, entityId, key, startTs, endTs, entitiesFutures); + findSum(entityId, key, startTs, endTs, entitiesFutures); break; case COUNT: - findCount(tenantId, entityId, key, startTs, endTs, entitiesFutures); + findCount(entityId, key, startTs, endTs, entitiesFutures); break; default: throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - protected void findCount(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + protected void findCount(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findCount( entityId.getId(), @@ -203,7 +203,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq endTs)); } - protected void findSum(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + protected void findSum(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findSum( entityId.getId(), @@ -212,7 +212,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq endTs)); } - protected void findMin(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + protected void findMin(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMin( entityId.getId(), @@ -226,7 +226,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq endTs)); } - protected void findMax(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + protected void findMax(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findStringMax( entityId.getId(), @@ -240,7 +240,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq endTs)); } - protected void findAvg(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { + protected void findAvg(EntityId entityId, String key, long startTs, long endTs, List> entitiesFutures) { Integer keyId = getOrSaveKeyId(key); entitiesFutures.add(tsKvRepository.findAvg( entityId.getId(), 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 a9277ec7e2..1d97aaddd8 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 @@ -127,7 +127,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() - .map(query -> findAllAsync(tenantId, entityId, query)) + .map(query -> findAllAsync(entityId, query)) .collect(Collectors.toList()); return Futures.transform(Futures.allAsList(futures), new Function>, List>() { @Nullable @@ -144,9 +144,9 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx }, service); } - protected abstract ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); + protected abstract ListenableFuture> findAllAsync(EntityId entityId, ReadTsKvQuery query); - protected abstract ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); + protected abstract ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query); protected ListenableFuture> getTskvEntriesFuture(ListenableFuture>> future) { return Futures.transform(future, new Function>, List>() { @@ -164,12 +164,12 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx }, service); } - protected ListenableFuture> findNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + protected ListenableFuture> findNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) { long startTs = 0; long endTs = query.getStartTs() - 1; ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1, Aggregation.NONE, DESC_ORDER); - return findAllAsync(tenantId, entityId, findNewLatestQuery); + return findAllAsync(entityId, findNewLatestQuery); } protected ListenableFuture getFindLatestFuture(EntityId entityId, String key) { @@ -189,7 +189,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx return Futures.immediateFuture(result); } - protected ListenableFuture getRemoveLatestFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { + protected ListenableFuture getRemoveLatestFuture(EntityId entityId, DeleteTsKvQuery query) { ListenableFuture latestFuture = getFindLatestFuture(entityId, query.getKey()); ListenableFuture booleanFuture = Futures.transform(latestFuture, tsKvEntry -> { @@ -217,7 +217,7 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx if (query.getRewriteLatestIfDeleted()) { ListenableFuture savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { if (isRemove) { - return getNewLatestEntryFuture(tenantId, entityId, query); + return getNewLatestEntryFuture(entityId, query); } return Futures.immediateFuture(null); }, service); @@ -296,8 +296,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx return keyId; } - private ListenableFuture getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - ListenableFuture> future = findNewLatestEntryFuture(tenantId, entityId, query); + private ListenableFuture getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) { + ListenableFuture> future = findNewLatestEntryFuture(entityId, query); return Futures.transformAsync(future, entryList -> { if (entryList.size() == 1) { return getSaveLatestFuture(entityId, entryList.get(0)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java index 738ae52a9d..1fa1fc4219 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/timescale/TimescaleInsertTsRepository.java @@ -37,8 +37,8 @@ import java.util.List; public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository { 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, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + - "ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; + "INSERT INTO ts_kv (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + + "ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; @Override public void saveOrUpdate(List> entities) { @@ -46,41 +46,40 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem @Override public void setValues(PreparedStatement ps, int i) throws SQLException { TimescaleTsKvEntity tsKvEntity = entities.get(i).getEntity(); - ps.setObject(1, tsKvEntity.getTenantId()); - ps.setObject(2, tsKvEntity.getEntityId()); - ps.setInt(3, tsKvEntity.getKey()); - ps.setLong(4, tsKvEntity.getTs()); + ps.setObject(1, tsKvEntity.getEntityId()); + ps.setInt(2, tsKvEntity.getKey()); + ps.setLong(3, tsKvEntity.getTs()); if (tsKvEntity.getBooleanValue() != null) { - ps.setBoolean(5, tsKvEntity.getBooleanValue()); - ps.setBoolean(10, tsKvEntity.getBooleanValue()); + ps.setBoolean(4, tsKvEntity.getBooleanValue()); + ps.setBoolean(9, tsKvEntity.getBooleanValue()); } else { - ps.setNull(5, Types.BOOLEAN); - ps.setNull(10, Types.BOOLEAN); + ps.setNull(4, Types.BOOLEAN); + ps.setNull(9, Types.BOOLEAN); } - ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); - ps.setString(11, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(5, replaceNullChars(tsKvEntity.getStrValue())); + ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); if (tsKvEntity.getLongValue() != null) { - ps.setLong(7, tsKvEntity.getLongValue()); - ps.setLong(12, tsKvEntity.getLongValue()); + ps.setLong(6, tsKvEntity.getLongValue()); + ps.setLong(11, tsKvEntity.getLongValue()); } else { - ps.setNull(7, Types.BIGINT); - ps.setNull(12, Types.BIGINT); + ps.setNull(6, Types.BIGINT); + ps.setNull(11, Types.BIGINT); } if (tsKvEntity.getDoubleValue() != null) { - ps.setDouble(8, tsKvEntity.getDoubleValue()); - ps.setDouble(13, tsKvEntity.getDoubleValue()); + ps.setDouble(7, tsKvEntity.getDoubleValue()); + ps.setDouble(12, tsKvEntity.getDoubleValue()); } else { - ps.setNull(8, Types.DOUBLE); - ps.setNull(13, Types.DOUBLE); + ps.setNull(7, Types.DOUBLE); + ps.setNull(12, Types.DOUBLE); } - ps.setString(9, replaceNullChars(tsKvEntity.getJsonValue())); - ps.setString(14, replaceNullChars(tsKvEntity.getJsonValue())); + ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue())); + ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue())); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java index ed784b96ba..28b666b03b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java @@ -36,7 +36,7 @@ public class AggregationRepository { public static final String FIND_SUM = "findSum"; public static final String FIND_COUNT = "findCount"; - public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket"; + public static final String FROM_WHERE_CLAUSE = "FROM ts_kv tskv WHERE tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.entity_id, tskv.key, tsBucket"; public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; @@ -52,43 +52,42 @@ public class AggregationRepository { private EntityManager entityManager; @Async - public CompletableFuture> findAvg(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findAvg(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") - List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG); + List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_AVG); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findMax(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findMax(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") - List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX); + List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MAX); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findMin(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findMin(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") - List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN); + List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_MIN); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findSum(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findSum(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") - List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM); + List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_SUM); return CompletableFuture.supplyAsync(() -> resultList); } @Async - public CompletableFuture> findCount(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { + public CompletableFuture> findCount(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs) { @SuppressWarnings("unchecked") - List resultList = getResultList(tenantId, entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT); + List resultList = getResultList(entityId, entityKey, timeBucket, startTs, endTs, FIND_COUNT); return CompletableFuture.supplyAsync(() -> resultList); } - private List getResultList(UUID tenantId, UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) { + private List getResultList(UUID entityId, int entityKey, long timeBucket, long startTs, long endTs, String query) { return entityManager.createNamedQuery(query) - .setParameter("tenantId", tenantId) .setParameter("entityId", entityId) .setParameter("entityKey", entityKey) .setParameter("timeBucket", timeBucket) 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 4ca53a337b..bf4cf5d9e6 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 @@ -88,24 +88,23 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - protected ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + protected ListenableFuture> findAllAsync(EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { - return findAllAsyncWithLimit(tenantId, entityId, query); + return findAllAsyncWithLimit(entityId, query); } else { long startTs = query.getStartTs(); long endTs = query.getEndTs(); long timeBucket = query.getInterval(); - ListenableFuture>> future = findAllAndAggregateAsync(tenantId, entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); + ListenableFuture>> future = findAllAndAggregateAsync(entityId, query.getKey(), startTs, endTs, timeBucket, query.getAggregation()); return getTskvEntriesFuture(future); } } @Override - protected ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { + protected ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { String strKey = query.getKey(); Integer keyId = getOrSaveKeyId(strKey); List timescaleTsKvEntities = tsKvRepository.findAllWithLimit( - tenantId.getId(), entityId.getId(), keyId, query.getStartTs(), @@ -117,8 +116,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements return Futures.immediateFuture(DaoUtil.convertDataList(timescaleTsKvEntities)); } - private ListenableFuture>> findAllAndAggregateAsync(TenantId tenantId, EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { - CompletableFuture> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId(), tenantId.getId()); + private ListenableFuture>> findAllAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long timeBucket, Aggregation aggregation) { + CompletableFuture> listCompletableFuture = switchAggregation(key, startTs, endTs, timeBucket, aggregation, entityId.getId()); SettableFuture> listenableFuture = SettableFuture.create(); listCompletableFuture.whenComplete((timescaleTsKvEntities, throwable) -> { if (throwable != null) { @@ -133,7 +132,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements timescaleTsKvEntities.forEach(entity -> { if (entity != null && entity.isNotEmpty()) { entity.setEntityId(entityId.getId()); - entity.setTenantId(tenantId.getId()); entity.setStrKey(key); result.add(Optional.of(DaoUtil.getData(entity))); } else { @@ -167,7 +165,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); - entity.setTenantId(tenantId.getId()); entity.setEntityId(entityId.getId()); entity.setTs(tsKvEntry.getTs()); entity.setKey(keyId); @@ -197,7 +194,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements Integer keyId = getOrSaveKeyId(strKey); return service.submit(() -> { tsKvRepository.delete( - tenantId.getId(), entityId.getId(), keyId, query.getStartTs(), @@ -208,7 +204,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements @Override public ListenableFuture removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return getRemoveLatestFuture(tenantId, entityId, query); + return getRemoveLatestFuture(entityId, query); } @Override @@ -216,27 +212,26 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements return service.submit(() -> null); } - private CompletableFuture> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId, UUID tenantId) { + private CompletableFuture> switchAggregation(String key, long startTs, long endTs, long timeBucket, Aggregation aggregation, UUID entityId) { switch (aggregation) { case AVG: - return findAvg(key, startTs, endTs, timeBucket, entityId, tenantId); + return findAvg(key, startTs, endTs, timeBucket, entityId); case MAX: - return findMax(key, startTs, endTs, timeBucket, entityId, tenantId); + return findMax(key, startTs, endTs, timeBucket, entityId); case MIN: - return findMin(key, startTs, endTs, timeBucket, entityId, tenantId); + return findMin(key, startTs, endTs, timeBucket, entityId); case SUM: - return findSum(key, startTs, endTs, timeBucket, entityId, tenantId); + return findSum(key, startTs, endTs, timeBucket, entityId); case COUNT: - return findCount(key, startTs, endTs, timeBucket, entityId, tenantId); + return findCount(key, startTs, endTs, timeBucket, entityId); default: throw new IllegalArgumentException("Not supported aggregation type: " + aggregation); } } - private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findCount(String key, long startTs, long endTs, long timeBucket, UUID entityId) { Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findCount( - tenantId, entityId, keyId, timeBucket, @@ -244,10 +239,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findSum(String key, long startTs, long endTs, long timeBucket, UUID entityId) { Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findSum( - tenantId, entityId, keyId, timeBucket, @@ -255,10 +249,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findMin(String key, long startTs, long endTs, long timeBucket, UUID entityId) { Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findMin( - tenantId, entityId, keyId, timeBucket, @@ -266,10 +259,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findMax(String key, long startTs, long endTs, long timeBucket, UUID entityId) { Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findMax( - tenantId, entityId, keyId, timeBucket, @@ -277,10 +269,9 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements endTs); } - private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId, UUID tenantId) { + private CompletableFuture> findAvg(String key, long startTs, long endTs, long timeBucket, UUID entityId) { Integer keyId = getOrSaveKeyId(key); return aggregationRepository.findAvg( - tenantId, entityId, keyId, timeBucket, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index fb9cb6f7fe..d4e80dc1f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -31,12 +31,10 @@ import java.util.UUID; @TimescaleDBTsDao public interface TsKvTimescaleRepository extends CrudRepository { - @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.tenantId = :tenantId " + - "AND tskv.entityId = :entityId " + + @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + "AND tskv.key = :entityKey " + "AND tskv.ts > :startTs AND tskv.ts <= :endTs") List findAllWithLimit( - @Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, @@ -44,12 +42,10 @@ public interface TsKvTimescaleRepository extends CrudRepository :startTs AND tskv.ts <= :endTs") - void delete(@Param("tenantId") UUID tenantId, - @Param("entityId") UUID entityId, + void delete(@Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, @Param("endTs") long endTs); diff --git a/dao/src/main/resources/sql/schema-timescale-idx.sql b/dao/src/main/resources/sql/schema-timescale-idx.sql deleted file mode 100644 index b9a3737d47..0000000000 --- a/dao/src/main/resources/sql/schema-timescale-idx.sql +++ /dev/null @@ -1,17 +0,0 @@ --- --- Copyright © 2016-2020 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. --- - -CREATE INDEX IF NOT EXISTS idx_tenant_ts_kv ON tenant_ts_kv(tenant_id, entity_id, key, ts); \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-timescale.sql b/dao/src/main/resources/sql/schema-timescale.sql index 7251d8be4e..b95c8b86ba 100644 --- a/dao/src/main/resources/sql/schema-timescale.sql +++ b/dao/src/main/resources/sql/schema-timescale.sql @@ -16,8 +16,7 @@ CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE; -CREATE TABLE IF NOT EXISTS tenant_ts_kv ( - tenant_id uuid NOT NULL, +CREATE TABLE IF NOT EXISTS ts_kv ( entity_id uuid NOT NULL, key int NOT NULL, ts bigint NOT NULL, @@ -26,7 +25,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv ( long_v bigint, dbl_v double precision, json_v json, - CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) + CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) ); CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 7ebab237a8..6d306c4e71 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -44,7 +44,7 @@ public class SqlDaoServiceTestSuite { // @ClassRule // public static CustomSqlUnit sqlUnit = new CustomSqlUnit( -// Arrays.asList("sql/schema-timescale.sql", "sql/schema-timescale-idx.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), +// Arrays.asList("sql/schema-timescale.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), // "sql/timescale/drop-all-tables.sql", // "sql-test.properties" // ); diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 08d018dc1b..ac921c0f4a 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -12,7 +12,7 @@ DROP TABLE IF EXISTS event; DROP TABLE IF EXISTS relation; DROP TABLE IF EXISTS tb_user; DROP TABLE IF EXISTS tenant; -DROP TABLE IF EXISTS tenant_ts_kv; +DROP TABLE IF EXISTS ts_kv; DROP TABLE IF EXISTS ts_kv_latest; DROP TABLE IF EXISTS user_credentials; DROP TABLE IF EXISTS widget_type; From 49be7d1ec3eac3a98dcc7cfd05498cbefcf9957e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 11 Mar 2020 15:16:17 +0200 Subject: [PATCH 256/261] Add support ticks to digital gauge --- ui/src/app/widget/lib/CanvasDigitalGauge.js | 57 ++++ ui/src/app/widget/lib/canvas-digital-gauge.js | 275 +++++++++++++----- 2 files changed, 262 insertions(+), 70 deletions(-) diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js index d450e09d9c..8f48292c78 100644 --- a/ui/src/app/widget/lib/CanvasDigitalGauge.js +++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js @@ -51,6 +51,10 @@ let defaultDigitalGaugeOptions = Object.assign({}, canvasGauges.GenericOptions, neonGlowBrightness: 0, + colorTicks: 'gray', + tickWidth: 4, + ticks: [], + isMobile: false }); @@ -133,6 +137,13 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge { } } + options.ticksValue = []; + for(let i = 0; i < options.ticks.length; i++){ + if(options.ticks[i] !== null){ + options.ticksValue.push(CanvasDigitalGauge.normalizeValue(options.ticks[i], options.minValue, options.maxValue)) + } + } + if (options.neonGlowBrightness) { options.neonColorTitle = tinycolor(options.colorTitle).brighten(options.neonGlowBrightness).toHexString(); options.neonColorLabel = tinycolor(options.colorLabel).brighten(options.neonGlowBrightness).toHexString(); @@ -729,6 +740,48 @@ function drawBarGlow(context, startX, startY, endX, endY, color, strokeWidth, is context.stroke(); } +function drawTickArc(context, tickValues, Cx, Cy, Ri, Rm, Ro, startAngle, endAngle, color, tickWidth) { + if(!tickValues.length) { + return; + } + + const strokeWidth = Ro - Ri; + context.beginPath(); + context.lineWidth = tickWidth; + context.strokeStyle = color; + for (let i = 0; i < tickValues.length; i++) { + var angle = startAngle + tickValues[i] * endAngle; + var x1 = Cx + (Ri + strokeWidth) * Math.cos(angle); + var y1 = Cy + (Ri + strokeWidth) * Math.sin(angle); + var x2 = Cx + Ri * Math.cos(angle); + var y2 = Cy + Ri * Math.sin(angle); + context.moveTo(x1, y1); + context.lineTo(x2, y2); + } + context.stroke(); +} + +function drawTickBar(context, tickValues, startX, startY, distanceBar, strokeWidth, isVertical, color, tickWidth) { + if(!tickValues.length) { + return; + } + + context.beginPath(); + context.lineWidth = tickWidth; + context.strokeStyle = color; + for (let i = 0; i < tickValues.length; i++) { + let tickValue = tickValues[i] * distanceBar; + if (isVertical) { + context.moveTo(startX - strokeWidth / 2, startY + tickValue - distanceBar); + context.lineTo(startX + strokeWidth / 2, startY + tickValue - distanceBar); + } else { + context.moveTo(startX + tickValue, startY); + context.lineTo(startX + tickValue, startY + strokeWidth); + } + } + context.stroke(); +} + function drawProgress(context, options, progress) { var neonColor; if (options.neonGlowBrightness) { @@ -759,6 +812,7 @@ function drawProgress(context, options, progress) { if (options.neonGlowBrightness && !options.isMobile) { drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, true, options.donutStartAngle, options.donutEndAngle); } + drawTickArc(context, options.ticksValue, Cx, Cy, Ri, Rm, Ro, options.donutStartAngle, options.donutEndAngle - options.donutStartAngle, options.colorTicks, options.tickWidth); } else if (options.gaugeType === 'arc') { if (options.neonGlowBrightness) { context.strokeStyle = neonColor; @@ -769,6 +823,7 @@ function drawProgress(context, options, progress) { if (options.neonGlowBrightness && !options.isMobile) { drawArcGlow(context, Cx, Cy, Ri, Rm, Ro, neonColor, progress, false); } + drawTickArc(context, options.ticksValue, Cx, Cy, Ri, Rm, Ro, Math.PI, Math.PI, options.colorTicks, options.tickWidth); } else if (options.gaugeType === 'horizontalBar') { if (options.neonGlowBrightness) { context.strokeStyle = neonColor; @@ -781,6 +836,7 @@ function drawProgress(context, options, progress) { drawBarGlow(context, barLeft, barTop + strokeWidth/2, barLeft + (barRight-barLeft)*progress, barTop + strokeWidth/2, neonColor, strokeWidth, false); } + drawTickBar(context, options.ticksValue, barLeft, barTop, barRight - barLeft, strokeWidth, false, options.colorTicks, options.tickWidth); } else if (options.gaugeType === 'verticalBar') { if (options.neonGlowBrightness) { context.strokeStyle = neonColor; @@ -793,6 +849,7 @@ function drawProgress(context, options, progress) { drawBarGlow(context, baseX + width/2, barBottom, baseX + width/2, barBottom - (barBottom-barTop)*progress, neonColor, strokeWidth, true); } + drawTickBar(context, options.ticksValue, baseX + width / 2, barTop, barTop - barBottom, strokeWidth, true, options.colorTicks, options.tickWidth); } } diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index c0eed09c20..12281e5361 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -62,6 +62,11 @@ export default class TbCanvasDigitalGauge { this.localSettings.fixedLevelColors = settings.fixedLevelColors || []; } + this.localSettings.showTicks = settings.showTicks || false; + this.localSettings.ticksValue = settings.ticksValue; + this.localSettings.tickWidth = settings.tickWidth || 4; + this.localSettings.colorTicks = settings.colorTicks || '#666'; + this.localSettings.decimals = angular.isDefined(dataKey.decimals) ? dataKey.decimals : ((angular.isDefined(settings.decimals) && settings.decimals !== null) ? settings.decimals : ctx.decimals); @@ -145,6 +150,10 @@ export default class TbCanvasDigitalGauge { gaugeColor: this.localSettings.gaugeColor, levelColors: this.localSettings.levelColors, + colorTicks: this.localSettings.colorTicks, + tickWidth: this.localSettings.tickWidth, + ticks: [], + title: this.localSettings.title, fontTitleSize: this.localSettings.titleFont.size, @@ -204,9 +213,81 @@ export default class TbCanvasDigitalGauge { if (this.localSettings.useFixedLevelColor) { if (this.localSettings.fixedLevelColors && this.localSettings.fixedLevelColors.length > 0) { this.localSettings.levelColors = this.settingLevelColorsSubscribe(this.localSettings.fixedLevelColors); - this.updateLevelColors(this.localSettings.levelColors); } } + if (this.localSettings.showTicks) { + if (this.localSettings.ticksValue && this.localSettings.ticksValue.length) { + this.localSettings.ticks = this.settingTicksSubscribe(this.localSettings.ticksValue); + } + } + this.updateSetting(); + } + + static generateDatasorce(ctx, datasources, entityAlias, attribute, settings){ + let entityAliasId = ctx.aliasController.getEntityAliasId(entityAlias); + if (!entityAliasId) { + throw new Error('Not valid entity aliase name ' + entityAlias); + } + + let datasource = datasources.filter((datasource) => { + return datasource.entityAliasId === entityAliasId; + })[0]; + + let dataKey = { + type: ctx.$scope.$injector.get('types').dataKeyType.attribute, + name: attribute, + label: attribute, + settings: [settings], + _hash: Math.random() + }; + + if (datasource) { + let findDataKey = datasource.dataKeys.filter((dataKey) => { + return dataKey.name === attribute; + })[0]; + + if (findDataKey) { + findDataKey.settings.push(settings); + } else { + datasource.dataKeys.push(dataKey) + } + } else { + datasource = { + type: ctx.$scope.$injector.get('types').datasourceType.entity, + name: entityAlias, + aliasName: entityAlias, + entityAliasId: entityAliasId, + dataKeys: [dataKey] + }; + datasources.push(datasource); + } + + return datasources; + } + + settingTicksSubscribe(options) { + let ticksDatasource = []; + let predefineTicks = []; + + for (let i = 0; i < options.length; i++) { + let tick = options[i]; + if (tick.valueSource === 'predefinedValue' && isFinite(tick.value)) { + predefineTicks.push(tick.value) + } else if (tick.entityAlias && tick.attribute) { + try { + ticksDatasource = TbCanvasDigitalGauge.generateDatasorce(this.ctx, ticksDatasource, tick.entityAlias, tick.attribute, predefineTicks.length); + } catch (e) { + continue; + } + predefineTicks.push(null); + } + } + + this.subscribeAttributes(ticksDatasource, 'ticks').then((subscription) => { + this.ticksSourcesSubscription = subscription; + }); + + return predefineTicks; } settingLevelColorsSubscribe(options) { @@ -220,50 +301,14 @@ export default class TbCanvasDigitalGauge { color: color }) } else if (levelSetting.entityAlias && levelSetting.attribute) { - let entityAliasId = this.ctx.aliasController.getEntityAliasId(levelSetting.entityAlias); - if (!entityAliasId) { - return; - } - - let datasource = levelColorsDatasource.filter((datasource) => { - return datasource.entityAliasId === entityAliasId; - })[0]; - - let dataKey = { - type: this.ctx.$scope.$injector.get('types').dataKeyType.attribute, - name: levelSetting.attribute, - label: levelSetting.attribute, - settings: [{ + try { + levelColorsDatasource = TbCanvasDigitalGauge.generateDatasorce(this.ctx, levelColorsDatasource, levelSetting.entityAlias, levelSetting.attribute, { color: color, index: predefineLevelColors.length - }], - _hash: Math.random() - }; - - if (datasource) { - let findDataKey = datasource.dataKeys.filter((dataKey) => { - return dataKey.name === levelSetting.attribute; - })[0]; - - if (findDataKey) { - findDataKey.settings.push({ - color: color, - index: predefineLevelColors.length - }); - } else { - datasource.dataKeys.push(dataKey) - } - } else { - datasource = { - type: this.ctx.$scope.$injector.get('types').datasourceType.entity, - name: levelSetting.entityAlias, - aliasName: levelSetting.entityAlias, - entityAliasId: entityAliasId, - dataKeys: [dataKey] - }; - levelColorsDatasource.push(datasource); + }); + } catch (e) { + return; } - predefineLevelColors.push(null); } } @@ -278,49 +323,63 @@ export default class TbCanvasDigitalGauge { } } - this.subscribeLevelColorsAttributes(levelColorsDatasource); + this.subscribeAttributes(levelColorsDatasource, 'levelColors').then((subscription) => { + this.levelColorSourcesSubscription = subscription; + }); return predefineLevelColors; } - updateLevelColors(levelColors) { - this.gauge.options.levelColors = levelColors; - this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); - this.gauge.update(); - } + subscribeAttributes(datasources, typeAttributes) { + if (!datasources.length) { + return this.ctx.$scope.$injector.get('$q').when(null); + } - subscribeLevelColorsAttributes(datasources) { - let TbCanvasDigitalGauge = this; let levelColorsSourcesSubscriptionOptions = { datasources: datasources, useDashboardTimewindow: false, type: this.ctx.$scope.$injector.get('types').widgetType.latest.value, callbacks: { onDataUpdated: (subscription) => { - for (let i = 0; i < subscription.data.length; i++) { - let keyData = subscription.data[i]; - if (keyData && keyData.data && keyData.data[0]) { - let attrValue = keyData.data[0][1]; - if (isFinite(attrValue)) { - for (let i = 0; i < keyData.dataKey.settings.length; i++) { - let setting = keyData.dataKey.settings[i]; - this.localSettings.levelColors[setting.index] = { - value: attrValue, - color: setting.color - }; - } - } - } - } - this.updateLevelColors(this.localSettings.levelColors); + this.updateAttribute(subscription.data, typeAttributes); } } }; - this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true).then( - (subscription) => { - TbCanvasDigitalGauge.levelColorSourcesSubscription = subscription; + + return this.ctx.subscriptionApi.createSubscription(levelColorsSourcesSubscriptionOptions, true); + } + + updateAttribute(data, typeAttributes) { + for (let i = 0; i < data.length; i++) { + let keyData = data[i]; + if (keyData && keyData.data && keyData.data[0]) { + let attrValue = keyData.data[0][1]; + if (isFinite(attrValue)) { + for (let i = 0; i < keyData.dataKey.settings.length; i++) { + let setting = keyData.dataKey.settings[i]; + switch (typeAttributes) { + case 'levelColors': + this.localSettings.levelColors[setting.index] = { + value: attrValue, + color: setting.color + }; + break; + case 'ticks': + this.localSettings.ticks[setting] = attrValue; + break; + } + } + } } - ); + } + this.updateSetting(); + } + + updateSetting() { + this.gauge.options.ticks = this.localSettings.ticks; + this.gauge.options.levelColors = this.localSettings.levelColors; + this.gauge.options = CanvasDigitalGauge.configure(this.gauge.options); + this.gauge.update(); } update() { @@ -526,6 +585,48 @@ export default class TbCanvasDigitalGauge { } } }, + "showTicks": { + "title": "Show ticks", + "type": "boolean", + "default": false + }, + "tickWidth": { + "title": "Width ticks", + "type": "number", + "default": 4 + }, + "colorTicks": { + "title": "Color ticks", + "type": "string", + "default": "#666" + }, + "ticksValue": { + "title": "The ticks predefined value", + "type": "array", + "items": { + "title": "tickValue", + "type": "object", + "properties": { + "valueSource": { + "title": "Value source", + "type": "string", + "default": "predefinedValue" + }, + "entityAlias": { + "title": "Source entity alias", + "type": "string" + }, + "attribute": { + "title": "Source entity attribute", + "type": "string" + }, + "value": { + "title": "Value (if predefined value is selected)", + "type": "number" + } + } + } + }, "animation": { "title": "Enable animation", "type": "boolean", @@ -771,6 +872,40 @@ export default class TbCanvasDigitalGauge { } ] }, + "showTicks", + { + "key": "tickWidth", + "condition": "model.showTicks === true" + }, + { + "key": "colorTicks", + "condition": "model.showTicks === true", + "type": "color" + }, + { + "key": "ticksValue", + "condition": "model.showTicks === true", + "items": [ + { + "key": "ticksValue[].valueSource", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "predefinedValue", + "label": "Predefined value (Default)" + }, + { + "value": "entityAttribute", + "label": "Value taken from entity attribute" + } + ] + }, + "ticksValue[].value", + "ticksValue[].entityAlias", + "ticksValue[].attribute" + ] + }, "animation", "animationDuration", { From 72ef0ede740c92ce6087c02785aad91b8e1ef89c Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 11 Mar 2020 16:36:08 +0200 Subject: [PATCH 257/261] Fix RPM preinst script --- application/src/main/scripts/control/rpm/preinst | 6 +++--- transport/coap/src/main/scripts/control/rpm/preinst | 6 +++--- transport/http/src/main/scripts/control/rpm/preinst | 6 +++--- transport/mqtt/src/main/scripts/control/rpm/preinst | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/application/src/main/scripts/control/rpm/preinst b/application/src/main/scripts/control/rpm/preinst index e19fc884c8..db6306e4ac 100644 --- a/application/src/main/scripts/control/rpm/preinst +++ b/application/src/main/scripts/control/rpm/preinst @@ -1,6 +1,6 @@ #!/bin/sh -getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} -getent passwd ${pkg.name} >/dev/null || \ -useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user} +getent passwd ${pkg.user} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \ -c "Thingsboard application" diff --git a/transport/coap/src/main/scripts/control/rpm/preinst b/transport/coap/src/main/scripts/control/rpm/preinst index e19fc884c8..db6306e4ac 100644 --- a/transport/coap/src/main/scripts/control/rpm/preinst +++ b/transport/coap/src/main/scripts/control/rpm/preinst @@ -1,6 +1,6 @@ #!/bin/sh -getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} -getent passwd ${pkg.name} >/dev/null || \ -useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user} +getent passwd ${pkg.user} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \ -c "Thingsboard application" diff --git a/transport/http/src/main/scripts/control/rpm/preinst b/transport/http/src/main/scripts/control/rpm/preinst index e19fc884c8..db6306e4ac 100644 --- a/transport/http/src/main/scripts/control/rpm/preinst +++ b/transport/http/src/main/scripts/control/rpm/preinst @@ -1,6 +1,6 @@ #!/bin/sh -getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} -getent passwd ${pkg.name} >/dev/null || \ -useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user} +getent passwd ${pkg.user} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \ -c "Thingsboard application" diff --git a/transport/mqtt/src/main/scripts/control/rpm/preinst b/transport/mqtt/src/main/scripts/control/rpm/preinst index e19fc884c8..db6306e4ac 100644 --- a/transport/mqtt/src/main/scripts/control/rpm/preinst +++ b/transport/mqtt/src/main/scripts/control/rpm/preinst @@ -1,6 +1,6 @@ #!/bin/sh -getent group ${pkg.name} >/dev/null || groupadd -r ${pkg.name} -getent passwd ${pkg.name} >/dev/null || \ -useradd -d ${pkg.installFolder} -g ${pkg.name} -M -r ${pkg.name} -s /sbin/nologin \ +getent group ${pkg.user} >/dev/null || groupadd -r ${pkg.user} +getent passwd ${pkg.user} >/dev/null || \ +useradd -d ${pkg.installFolder} -g ${pkg.user} -M -r ${pkg.user} -s /sbin/nologin \ -c "Thingsboard application" From c4b11f985723b26fdef794fe578e2182fed71fc7 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 11 Mar 2020 16:45:25 +0200 Subject: [PATCH 258/261] Add default value --- ui/src/app/widget/lib/canvas-digital-gauge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index 12281e5361..b6ab75e643 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -63,7 +63,7 @@ export default class TbCanvasDigitalGauge { } this.localSettings.showTicks = settings.showTicks || false; - this.localSettings.ticksValue = settings.ticksValue; + this.localSettings.ticksValue = settings.ticksValue || []; this.localSettings.tickWidth = settings.tickWidth || 4; this.localSettings.colorTicks = settings.colorTicks || '#666'; From 2f8ea30846ac5e3973c832602dacee0caf84b71e Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 11 Mar 2020 16:53:05 +0200 Subject: [PATCH 259/261] Add init value --- ui/src/app/widget/lib/canvas-digital-gauge.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js index b6ab75e643..3d9caa2417 100644 --- a/ui/src/app/widget/lib/canvas-digital-gauge.js +++ b/ui/src/app/widget/lib/canvas-digital-gauge.js @@ -63,6 +63,7 @@ export default class TbCanvasDigitalGauge { } this.localSettings.showTicks = settings.showTicks || false; + this.localSettings.ticks = []; this.localSettings.ticksValue = settings.ticksValue || []; this.localSettings.tickWidth = settings.tickWidth || 4; this.localSettings.colorTicks = settings.colorTicks || '#666'; @@ -152,7 +153,7 @@ export default class TbCanvasDigitalGauge { colorTicks: this.localSettings.colorTicks, tickWidth: this.localSettings.tickWidth, - ticks: [], + ticks: this.localSettings.ticks, title: this.localSettings.title, From 515dc983d3deff4e320eebd6219b6d378d04d24c Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Mar 2020 18:07:42 +0200 Subject: [PATCH 260/261] Improvements/kafka rule node (#2505) * added metadata key-values as kafka headers * added default charset to configuration * fix typo --- .../rule/engine/kafka/TbKafkaNode.java | 44 ++++++++++++++----- .../kafka/TbKafkaNodeConfiguration.java | 5 +++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 216fea54e3..267e8711aa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -16,16 +16,21 @@ package org.thingsboard.rule.engine.kafka; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.BooleanUtils; import org.apache.kafka.clients.producer.*; +import org.apache.kafka.common.header.Headers; +import org.apache.kafka.common.header.internals.RecordHeader; +import org.apache.kafka.common.header.internals.RecordHeaders; 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 java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Properties; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicInteger; @Slf4j @RuleNode( @@ -46,8 +51,11 @@ public class TbKafkaNode implements TbNode { private static final String PARTITION = "partition"; private static final String TOPIC = "topic"; private static final String ERROR = "error"; + public static final String TB_MSG_MD_PREFIX = "tb_msg_md_"; private TbKafkaNodeConfiguration config; + private boolean addMetadataKeyValuesAsKafkaHeaders; + private Charset toBytesCharset; private Producer producer; @@ -66,8 +74,10 @@ public class TbKafkaNode implements TbNode { properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, config.getBufferMemory()); if (config.getOtherProperties() != null) { config.getOtherProperties() - .forEach((k,v) -> properties.put(k, v)); + .forEach(properties::put); } + addMetadataKeyValuesAsKafkaHeaders = BooleanUtils.toBooleanDefaultIfNull(config.isAddMetadataKeyValuesAsKafkaHeaders(), false); + toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8; try { this.producer = new KafkaProducer<>(properties); } catch (Exception e) { @@ -79,16 +89,16 @@ public class TbKafkaNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); try { - producer.send(new ProducerRecord<>(topic, msg.getData()), - (metadata, e) -> { - if (metadata != null) { - TbMsg next = processResponse(ctx, msg, metadata); - ctx.tellNext(next, TbRelationTypes.SUCCESS); - } else { - TbMsg next = processException(ctx, msg, e); - ctx.tellFailure(next, e); - } - }); + if (!addMetadataKeyValuesAsKafkaHeaders) { + producer.send(new ProducerRecord<>(topic, msg.getData()), + (metadata, e) -> processRecord(ctx, msg, metadata, e)); + } else { + Headers headers = new RecordHeaders(); + msg.getMetaData().values().forEach((key, value) -> headers.add(new RecordHeader(TB_MSG_MD_PREFIX + key, value.getBytes(toBytesCharset)))); + producer.send(new ProducerRecord<>(topic, null, null, null, msg.getData(), headers), + (metadata, e) -> processRecord(ctx, msg, metadata, e)); + } + } catch (Exception e) { ctx.tellFailure(msg, e); } @@ -105,6 +115,16 @@ public class TbKafkaNode implements TbNode { } } + private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { + if (metadata != null) { + TbMsg next = processResponse(ctx, msg, metadata); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + TbMsg next = processException(ctx, msg, e); + ctx.tellFailure(next, e); + } + } + private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { TbMsgMetaData metaData = origMsg.getMetaData().copy(); metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java index 1e8fe5b0c7..a1d4eedbb4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java @@ -36,6 +36,9 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration otherProperties; + private boolean addMetadataKeyValuesAsKafkaHeaders; + private String kafkaHeadersCharset; + @Override public TbKafkaNodeConfiguration defaultConfiguration() { TbKafkaNodeConfiguration configuration = new TbKafkaNodeConfiguration(); @@ -49,6 +52,8 @@ public class TbKafkaNodeConfiguration implements NodeConfiguration Date: Wed, 11 Mar 2020 18:08:28 +0200 Subject: [PATCH 261/261] improvements/kafka-rule-node-ui (#2506) * added ability to set metadata key-pairs as record headers in kafka node * added default charset if it is undefined --- .../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 9738a92ab7..db0d0cdbb0 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(103)},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 }}
    tb.rulenode.relation-types-list-hint
    "},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){function t(a){if(n[a])return n[a].exports;var i=n[a]={exports:{},id:a,loaded:!1};return e[a].call(i.exports,i,i.exports,t),i.loaded=!0,i.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),a=e[t[0]];return function(e,t,i){a.apply(this,[e,t,i].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(103)},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 }}
    tb.rulenode.relation-types-list-hint
    "},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
    {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}
    tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
    {{charset.name | translate}}
    '},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.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 }} {{tlsVersion}}
    '},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.last-level-relation' | translate}}
    {{ ('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="
    {{'alarm.display-status.' + item | translate}} {{'alarm.display-status.' + $chip | translate}}
    "},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.last-level-relation' | translate}}
    {{ ('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.configuration.hasOwnProperty("relationTypes")||(a.configuration.relationTypes=[])},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(75),r=i(a),o=n(53),l=i(o),s=n(58),d=i(s),u=n(55),c=i(u),m=n(54),g=i(m),p=n(62),f=i(p),b=n(69),v=i(b),y=n(70),h=i(y),q=n(68),x=i(q),$=n(61),k=i($),T=n(73),C=i(T),w=n(74),M=i(w),N=n(67),S=i(N),_=n(63),E=i(_),F=n(72),P=i(F),A=n(65),V=i(A),I=n(64),j=i(I),O=n(52),D=i(O),L=n(76),R=i(L),K=n(57),U=i(K),z=n(56),H=i(z),B=n(71),G=i(B),Y=n(59),Q=i(Y),W=n(66),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",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",R.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.tlsVersions=["TLSv1.0","TLSv1.1","TLSv1.2","TLSv1.3"],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(84),r=i(a),o=n(85),l=i(o),s=n(80),d=i(s),u=n(86),c=i(u),m=n(79),g=i(m),p=n(87),f=i(p),b=n(82),v=i(b),y=n(81),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),n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.alarmStatusList=[];for(var s in t.alarmStatus)n.alarmStatusList.push(t.alarmStatus[s]);r.$render=function(){n.configuration=r.$viewValue},n.getAlarmStatusList=function(){return n.alarmStatusList.filter(function(e){return n.configuration.alarmStatusList.indexOf(e)===-1})},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","types"],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);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(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.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(41),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(42),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(95),r=i(a),o=n(93),l=i(o),s=n(96),d=i(s),u=n(90),c=i(u),m=n(94),g=i(m),p=n(89),f=i(p),b=n(91),v=i(b),y=n(88),h=i(y);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).directive("tbFilterNodeCheckAlarmStatusConfig",h.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(47),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(48),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(49),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(99),r=i(a),o=n(101),l=i(o),s=n(102),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(50),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(51),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(106),r=i(a),o=n(92),l=i(o),s=n(83),d=i(s),u=n(100),c=i(u),m=n(60),g=i(m),p=n(78),f=i(p),b=n(98),v=i(b),y=n(77),h=i(y),q=n(97),x=i(q),$=n(105),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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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","alarm-statuses-filter":"Alarm statuses filter","alarm-statuses-required":"Alarm statuses 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","tls-version":"TLS version","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(104),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.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="
    {{'alarm.display-status.' + item | translate}} {{'alarm.display-status.' + $chip | translate}}
    "},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.last-level-relation' | translate}}
    {{ ('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 a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(6),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(7),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(8),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.hasOwnProperty("relationTypes")||(i.configuration.relationTypes=[])},i.testDetailsBuildJs=function(e){var n=angular.copy(i.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(9),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(10),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(11),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n,a){var i=function(i,r,l,s){var d=o.default;r.html(d),i.types=n,i.originator=null,i.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(i.configuration)}),s.$render=function(){i.configuration=s.$viewValue,i.configuration.originatorId&&i.configuration.originatorType?i.originator={id:i.configuration.originatorId,entityType:i.configuration.originatorType}:i.originator=null,i.$watch("originator",function(e,t){angular.equals(e,t)||(i.originator?(s.$viewValue.originatorId=i.originator.id,s.$viewValue.originatorType=i.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},i.testScript=function(e){var n=angular.copy(i.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}i.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(1);var r=n(12),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(13),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(75),r=a(i),o=n(53),l=a(o),s=n(58),d=a(s),u=n(55),c=a(u),m=n(54),g=a(m),p=n(62),f=a(p),b=n(69),v=a(b),y=n(70),h=a(y),q=n(68),k=a(q),x=n(61),$=a(x),T=n(73),C=a(T),w=n(74),M=a(w),S=n(67),N=a(S),_=n(63),F=a(_),E=n(72),P=a(E),A=n(65),V=a(A),I=n(64),O=a(I),j=n(52),D=a(j),L=n(76),R=a(L),K=n(57),U=a(K),z=n(56),H=a(z),B=n(71),G=a(B),Y=n(59),Q=a(Y),W=n(66),J=a(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",k.default).directive("tbActionNodeKafkaConfig",$.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",N.default).directive("tbActionNodeMqttConfig",F.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",V.default).directive("tbActionNodeMsgCountConfig",O.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",R.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 a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.ackValues=["all","-1","0","1"],n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue,n.configuration.hasOwnProperty("kafkaHeadersCharset")||(n.configuration.kafkaHeadersCharset="UTF-8")},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(14),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(15),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var i=n.target.result;i&&i.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=i),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=i),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=i)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}i.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i,n(2);var r=n(16),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(17),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(18),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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 a=t.target.result;a&&a.length>0&&(n.configuration.serviceAccountKeyFileName=e.name,n.configuration.serviceAccountKey=a),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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(19),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){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)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(20),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(21),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(22),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(23),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(24),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.tlsVersions=["TLSv1.0","TLSv1.1","TLSv1.2","TLSv1.3"],t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(25),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(26),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(27),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(28),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(29),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(30),o=a(r)},function(e,t){"use strict";function n(e){var t=function(t,n,a,i){n.html("
    "),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$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 a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(31),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(32),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(33),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s);var d=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}i.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(34),o=a(r);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(84),r=a(i),o=n(85),l=a(o),s=n(80),d=a(s),u=n(86),c=a(u),m=n(79),g=a(m),p=n(87),f=a(p),b=n(82),v=a(b),y=n(81),h=a(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 a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(35),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(36),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(37),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(38),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.html(l),n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.alarmStatusList=[];for(var s in t.alarmStatus)n.alarmStatusList.push(t.alarmStatus[s]);r.$render=function(){n.configuration=r.$viewValue},n.getAlarmStatusList=function(){return n.alarmStatusList.filter(function(e){return n.configuration.alarmStatusList.indexOf(e)===-1})},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(39),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(40),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(41),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(42),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(95),r=a(i),o=n(93),l=a(o),s=n(96),d=a(s),u=n(90),c=a(u),m=n(94),g=a(m),p=n(89),f=a(p),b=n(91),v=a(b),y=n(88),h=a(y);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).directive("tbFilterNodeCheckAlarmStatusConfig",h.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,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)}),i.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),i.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=i,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||i.$setViewValue(t.query)}),i.$render=function(){if(i.$viewValue){var e=i.$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}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(47),o=a(r);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(48),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e,t){var n=function(n,a,i,r){var l=o.default;a.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(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}i.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(49),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(99),r=a(i),o=n(101),l=a(o),s=n(102),d=a(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 a(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){var a=function(a,i,r,l){var s=o.default;i.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var i=angular.copy(a.configuration.jsScript);n.testNodeScript(e,i,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}i.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(50),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function i(e){var t=function(t,n,a,i){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||i.$setViewValue(t.configuration)}),i.$render=function(){t.configuration=i.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}i.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(51),o=a(r)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var i=n(106),r=a(i),o=n(92),l=a(o),s=n(83),d=a(s),u=n(100),c=a(u),m=n(60),g=a(m),p=n(78),f=a(p),b=n(98),v=a(b),y=n(77),h=a(y),q=n(97),k=a(q),x=n(105),$=a(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",k.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","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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","alarm-statuses-filter":"Alarm statuses filter","alarm-statuses-required":"Alarm statuses 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","tls-version":"TLS version","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","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16"},"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 a(e){return e&&e.__esModule?e:{default:e}}function i(e){(0,o.default)(e)}i.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=i;var r=n(104),o=a(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"}},toBytesStandartCharsetTypes:{"US-ASCII":{value:"US-ASCII",name:"tb.rulenode.charset-us-ascii"},"ISO-8859-1":{value:"ISO-8859-1",name:"tb.rulenode.charset-iso-8859-1"},"UTF-8":{value:"UTF-8",name:"tb.rulenode.charset-utf-8"},"UTF-16BE":{value:"UTF-16BE",name:"tb.rulenode.charset-utf-16be"},"UTF-16LE":{value:"UTF-16LE",name:"tb.rulenode.charset-utf-16le"},"UTF-16":{value:"UTF-16",name:"tb.rulenode.charset-utf-16"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file
  • {{ column.title }}{{ column.title }}