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": "
",
- "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": "",
+ "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/tools/pom.xml b/tools/pom.xml
index 7402fe4708..0d044365a0 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -51,6 +51,39 @@
com.google.guava
guava
+
+ org.apache.cassandra
+ cassandra-all
+ 3.11.4
+
+
+ com.datastax.cassandra
+ cassandra-driver-core
+
+
+ commons-io
+ commons-io
+
+
+
+
+
+ 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