diff --git a/application/pom.xml b/application/pom.xml
index d06fbfa718..6efb019f75 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -28,14 +28,17 @@
jar
Thingsboard Server Application
- http://thingsboard.org
+ https://thingsboard.io
+ Open-source IoT Platform - Device management, data collection, processing and visualization
+
UTF-8
${basedir}/..
thingsboard
- /var/log/${pkg.name}
+ /var/log/${pkg.name}
/usr/share/${pkg.name}
+ ${project.build.directory}/windows
@@ -196,6 +199,13 @@
io.springfox
springfox-swagger2
+
+ com.sun.winsw
+ winsw
+ bin
+ exe
+ provided
+
org.thingsboard
tools
@@ -291,6 +301,38 @@
true
+
+ src/main/filters/unix.properties
+
+
+
+
+ copy-win-conf
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.win.dist}/conf
+
+
+ src/main/resources
+
+ logback.xml
+
+ false
+
+
+ src/main/conf
+
+ thingsboard.conf
+
+ true
+
+
+
+ src/main/filters/windows.properties
+
@@ -307,6 +349,28 @@
true
+
+ src/main/filters/unix.properties
+
+
+
+
+ copy-windows-control
+ process-resources
+
+ copy-resources
+
+
+ ${pkg.win.dist}
+
+
+ src/main/scripts/windows
+ true
+
+
+
+ src/main/filters/windows.properties
+
@@ -361,6 +425,25 @@
+
+ copy-winsw-service
+ package
+
+ copy
+
+
+
+
+ com.sun.winsw
+ winsw
+ bin
+ exe
+ service.exe
+
+
+ ${pkg.win.dist}
+
+
@@ -385,7 +468,7 @@
true
${pkg.installFolder}/conf
- ${pkg.logFolder}
+ ${pkg.unixLogFolder}
${pkg.name}.out
@@ -412,7 +495,7 @@
-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}
-PpkgName=${pkg.name}
-PpkgInstallFolder=${pkg.installFolder}
- -PpkgLogFolder=${pkg.logFolder}
+ -PpkgLogFolder=${pkg.unixLogFolder}
@@ -424,6 +507,25 @@
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+ ${pkg.name}
+
+ src/main/assembly/windows.xml
+
+
+
+
+ assembly
+ package
+
+ single
+
+
+
+
org.xolstice.maven.plugins
protobuf-maven-plugin
@@ -434,4 +536,14 @@
+
+
+ jenkins
+ Jenkins Repository
+ http://repo.jenkins-ci.org/releases
+
+ false
+
+
+
diff --git a/application/src/main/assembly/windows.xml b/application/src/main/assembly/windows.xml
new file mode 100644
index 0000000000..023c4350a8
--- /dev/null
+++ b/application/src/main/assembly/windows.xml
@@ -0,0 +1,79 @@
+
+
+ windows
+
+
+ zip
+
+
+
+
+
+ ${pkg.win.dist}
+ logs
+
+ */**
+
+
+
+ ${pkg.win.dist}/conf
+ conf
+ windows
+
+
+ ${project.build.directory}/extensions
+ extensions
+
+
+ ${project.build.directory}/data
+ data
+
+
+
+
+
+ ${project.build.directory}/${project.build.finalName}-boot.${project.packaging}
+ lib
+ ${pkg.name}.jar
+
+
+ ${pkg.win.dist}/service.exe
+
+ ${pkg.name}.exe
+
+
+ ${pkg.win.dist}/service.xml
+
+ ${pkg.name}.xml
+ windows
+
+
+ ${pkg.win.dist}/install.bat
+
+ windows
+
+
+ ${pkg.win.dist}/uninstall.bat
+
+ windows
+
+
+
diff --git a/application/src/main/filters/unix.properties b/application/src/main/filters/unix.properties
new file mode 100644
index 0000000000..8967278673
--- /dev/null
+++ b/application/src/main/filters/unix.properties
@@ -0,0 +1 @@
+pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
diff --git a/application/src/main/filters/windows.properties b/application/src/main/filters/windows.properties
new file mode 100644
index 0000000000..a6e48d91ba
--- /dev/null
+++ b/application/src/main/filters/windows.properties
@@ -0,0 +1,2 @@
+pkg.logFolder=${BASE}\\logs
+pkg.winWrapperLogFolder=%BASE%\\logs
diff --git a/application/src/main/scripts/windows/install.bat b/application/src/main/scripts/windows/install.bat
new file mode 100644
index 0000000000..97c4feb503
--- /dev/null
+++ b/application/src/main/scripts/windows/install.bat
@@ -0,0 +1,90 @@
+@ECHO OFF
+
+setlocal ENABLEEXTENSIONS
+
+IF %PROCESSOR_ARCHITECTURE%==AMD64 GOTO CHECK_JAVA_64
+IF %PROCESSOR_ARCHITECTURE%==x86 GOTO CHECK_JAVA_32
+
+@ECHO Detecting Java version installed.
+:CHECK_JAVA_64
+@ECHO Detecting if it is 64 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+ set ValueName=%%A
+ set ValueType=%%B
+ set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+ FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+ set ValueName2=%%A
+ set ValueType2=%%B
+ set JRE_PATH2=%%C
+
+ if defined ValueName2 (
+ set ValueName = %ValueName2%
+ set ValueType = %ValueType2%
+ set ValueValue = %JRE_PATH2%
+ )
+ )
+)
+
+IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
+IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED
+
+:CHECK_JAVA_32
+@ECHO Detecting if it is 32 bit machine
+set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"
+set VALUE_NAME=CurrentVersion
+
+FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+ set ValueName=%%A
+ set ValueType=%%B
+ set ValueValue=%%C
+)
+@ECHO CurrentVersion %ValueValue%
+
+SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
+SET VALUE_NAME=JavaHome
+
+if defined ValueName (
+ FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
+ set ValueName2=%%A
+ set ValueType2=%%B
+ set JRE_PATH2=%%C
+
+ if defined ValueName2 (
+ set ValueName = %ValueName2%
+ set ValueType = %ValueType2%
+ set ValueValue = %JRE_PATH2%
+ )
+ )
+)
+
+IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED
+
+:JAVA_INSTALLED
+
+@ECHO Java 1.8 found!
+@ECHO Installing ${pkg.name} ...
+${pkg.name}.exe install
+
+@ECHO DONE.
+
+GOTO END
+
+:JAVA_NOT_INSTALLED
+@ECHO Java 1.8 or above is not installed
+@ECHO Please go to https://java.com/ and install Java. Then retry installation.
+PAUSE
+GOTO END
+
+:END
+
+
diff --git a/application/src/main/scripts/windows/service.xml b/application/src/main/scripts/windows/service.xml
new file mode 100644
index 0000000000..b2acc45a8a
--- /dev/null
+++ b/application/src/main/scripts/windows/service.xml
@@ -0,0 +1,12 @@
+
+ ${pkg.name}
+ ${project.name}
+ ${project.description}
+ %BASE%\conf
+ ${pkg.winWrapperLogFolder}
+ rotate
+
+ java
+ -jar
+ %BASE%\lib\${pkg.name}.jar
+
diff --git a/application/src/main/scripts/windows/uninstall.bat b/application/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000000..d07cf870ca
--- /dev/null
+++ b/application/src/main/scripts/windows/uninstall.bat
@@ -0,0 +1,9 @@
+@ECHO OFF
+
+@ECHO Stopping ${pkg.name} ...
+net stop ${pkg.name}
+
+@ECHO Uninstalling ${pkg.name} ...
+${pkg.name}.exe uninstall
+
+@ECHO DONE.
\ No newline at end of file
diff --git a/dao/src/main/resources/system-data.cql b/dao/src/main/resources/system-data.cql
index 5694926591..a687a4ec8b 100644
--- a/dao/src/main/resources/system-data.cql
+++ b/dao/src/main/resources/system-data.cql
@@ -79,7 +79,7 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'label_widget',
INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table',
-'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"\n \n \n \n \n \n | Timestamp | \n {{ h.label }} | \n
\n \n \n \n | \n {{ $index === 0 ? (d | date : ''yyyy-MM-dd HH:mm:ss'') : d }}\n | \n
\n \n
\n \n \n \n \n","templateCss":"table.md-table thead.md-head>tr.md-row {\n height: 40px;\n}\n\ntable.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {\n height: 38px;\n}\n\n.md-table-pagination>* {\n height: 46px;\n}\n","controllerScript":"var filter;\n\nfns.init = function(containerElement, settings, datasources,\n data, scope) {\n \n filter = scope.$injector.get(\"$filter\");\n \n scope.sources = [];\n scope.sourceIndex = 0;\n \n var keyOffset = 0;\n for (var ds in datasources) {\n var source = {};\n var datasource = datasources[ds];\n source.keyStartIndex = keyOffset;\n keyOffset += datasource.dataKeys.length;\n source.keyEndIndex = keyOffset;\n source.label = datasource.name;\n source.data = [];\n source.rawData = [];\n source.query = {\n limit: 5,\n page: 1,\n order: ''-0''\n }\n source.ts = {\n header: [],\n count: 0,\n data: [],\n stylesInfo: []\n }\n for (var a = 0; a < datasource.dataKeys.length; a++ ) {\n var dataKey = datasource.dataKeys[a];\n var keySettings = dataKey.settings;\n source.ts.header.push({\n index: a+1,\n label: dataKey.label\n });\n\n var cellStyleFunction = null;\n var useCellStyleFunction = false;\n \n if (keySettings.useCellStyleFunction === true) {\n if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {\n try {\n cellStyleFunction = new Function(''value'', keySettings.cellStyleFunction);\n useCellStyleFunction = true;\n } catch (e) {\n cellStyleFunction = null;\n useCellStyleFunction = false;\n }\n }\n }\n\n source.ts.stylesInfo.push({\n useCellStyleFunction: useCellStyleFunction,\n cellStyleFunction: cellStyleFunction\n });\n }\n scope.sources.push(source);\n }\n\n scope.onPaginate = function(source) {\n updatePage(source);\n }\n \n scope.onReorder = function(source) {\n reorder(source);\n updatePage(source);\n }\n \n scope.cellStyle = function(source, index, value) {\n var style = {};\n if (index > 0) {\n var styleInfo = source.ts.stylesInfo[index-1];\n if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {\n try {\n style = styleInfo.cellStyleFunction(value);\n } catch (e) {\n style = {};\n }\n }\n }\n return style;\n }\n \n scope.$watch(''sourceIndex'', function(newIndex, oldIndex) {\n if (newIndex != oldIndex) {\n updateSourceData(scope.sources[scope.sourceIndex]);\n } \n });\n \n scope.$apply();\n}\n\nfunction updatePage(source) {\n var startIndex = source.query.limit * (source.query.page - 1);\n source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);\n}\n\nfunction reorder(source) {\n source.data = filter(''orderBy'')(source.data, source.query.order);\n}\n\nfunction convertData(data) {\n var rows = [];\n var count = data[0].data.length;\n for (var i = 0; i < count; i++) {\n var row = [];\n for (var d = 0; d < data.length; d++) {\n var columnData = data[d].data;\n var cellData = columnData[i];\n if (d === 0) {\n row.push(cellData[0]);\n }\n row.push(cellData[1]);\n }\n rows.push(row);\n }\n return rows;\n}\n\nfunction updateSourceData(source) {\n source.data = convertData(source.rawData);\n source.ts.count = source.data.length;\n reorder(source);\n updatePage(source);\n}\n\nfns.redraw = function(containerElement, width, height, data,\n timeWindow, sizeChanged, scope) {\n for (var s in scope.sources) {\n var source = scope.sources[s];\n source.rawData = data.slice(source.keyStartIndex, source.keyEndIndex);\n }\n updateSourceData(scope.sources[scope.sourceIndex]);\n scope.$apply();\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{}","dataKeySettingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\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 \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"var percent = (value + 60)/120 * 100;\\nvar color = tinycolor.mix(''blue'', ''red'', amount = percent);\\ncolor.setAlpha(.5);\\nreturn {\\n paddingLeft: ''20px'',\\n color: ''#ffffff'',\\n background: color.toRgbString(),\\n fontSize: ''18px''\\n};\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"var percent = value;\\nvar backgroundColor = tinycolor(''blue'');\\nbackgroundColor.setAlpha(value/100);\\nvar color = ''blue'';\\nif (value > 50) {\\n color = ''white'';\\n}\\n\\nreturn {\\n paddingLeft: ''20px'',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: ''18px''\\n};\"},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Timeseries table\"}"}',
+'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"\n \n \n \n \n \n | Timestamp | \n {{ h.label }} | \n
\n \n \n \n | 0 || ($index === 0 && showTimestamp)\" md-cell ng-repeat=\"d in row track by $index\" ng-style=\"cellStyle(source, $index, d)\" ng-bind-html=\"cellContent(source, $index, row, d)\">\n | \n
\n \n
\n \n \n \n \n","templateCss":"table.md-table thead.md-head>tr.md-row {\n height: 40px;\n}\n\ntable.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {\n height: 38px;\n}\n\n.md-table-pagination>* {\n height: 46px;\n}\n","controllerScript":"var filter;\n\nfns.init = function(containerElement, settings, datasources,\n data, scope) {\n \n filter = scope.$injector.get(\"$filter\");\n \n scope.sources = [];\n scope.sourceIndex = 0;\n scope.showTimestamp = settings.showTimestamp !== false;\n \n var keyOffset = 0;\n for (var ds in datasources) {\n var source = {};\n var datasource = datasources[ds];\n source.keyStartIndex = keyOffset;\n keyOffset += datasource.dataKeys.length;\n source.keyEndIndex = keyOffset;\n source.label = datasource.name;\n source.data = [];\n source.rawData = [];\n source.query = {\n limit: 5,\n page: 1,\n order: ''-0''\n }\n source.ts = {\n header: [],\n count: 0,\n data: [],\n stylesInfo: [],\n contentsInfo: [],\n rowDataTemplate: {}\n }\n source.ts.rowDataTemplate[''Timestamp''] = null;\n for (var a = 0; a < datasource.dataKeys.length; a++ ) {\n var dataKey = datasource.dataKeys[a];\n var keySettings = dataKey.settings;\n source.ts.header.push({\n index: a+1,\n label: dataKey.label\n });\n source.ts.rowDataTemplate[dataKey.label] = null;\n\n var cellStyleFunction = null;\n var useCellStyleFunction = false;\n \n if (keySettings.useCellStyleFunction === true) {\n if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {\n try {\n cellStyleFunction = new Function(''value'', keySettings.cellStyleFunction);\n useCellStyleFunction = true;\n } catch (e) {\n cellStyleFunction = null;\n useCellStyleFunction = false;\n }\n }\n }\n\n source.ts.stylesInfo.push({\n useCellStyleFunction: useCellStyleFunction,\n cellStyleFunction: cellStyleFunction\n });\n \n var cellContentFunction = null;\n var useCellContentFunction = false;\n \n if (keySettings.useCellContentFunction === true) {\n if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {\n try {\n cellContentFunction = new Function(''value, rowData, filter'', keySettings.cellContentFunction);\n useCellContentFunction = true;\n } catch (e) {\n cellContentFunction = null;\n useCellContentFunction = false;\n }\n }\n }\n \n source.ts.contentsInfo.push({\n useCellContentFunction: useCellContentFunction,\n cellContentFunction: cellContentFunction\n });\n \n }\n scope.sources.push(source);\n }\n\n scope.onPaginate = function(source) {\n updatePage(source);\n }\n \n scope.onReorder = function(source) {\n reorder(source);\n updatePage(source);\n }\n \n scope.cellStyle = function(source, index, value) {\n var style = {};\n if (index > 0) {\n var styleInfo = source.ts.stylesInfo[index-1];\n if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {\n try {\n style = styleInfo.cellStyleFunction(value);\n } catch (e) {\n style = {};\n }\n }\n }\n return style;\n }\n\n scope.cellContent = function(source, index, row, value) {\n if (index === 0) {\n return filter(''date'')(value, ''yyyy-MM-dd HH:mm:ss'');\n } else {\n var strContent = '''';\n if (value) {\n strContent = ''''+value;\n }\n var content = strContent;\n var contentInfo = source.ts.contentsInfo[index-1];\n if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {\n try {\n var rowData = source.ts.rowDataTemplate;\n rowData[''Timestamp''] = row[0];\n for (var h in source.ts.header) {\n var headerInfo = source.ts.header[h];\n rowData[headerInfo.label] = row[headerInfo.index];\n }\n content = contentInfo.cellContentFunction(value, rowData, filter);\n } catch (e) {\n content = strContent;\n }\n } \n return content;\n }\n }\n \n scope.$watch(''sourceIndex'', function(newIndex, oldIndex) {\n if (newIndex != oldIndex) {\n updateSourceData(scope.sources[scope.sourceIndex]);\n } \n });\n \n scope.$apply();\n}\n\nfunction updatePage(source) {\n var startIndex = source.query.limit * (source.query.page - 1);\n source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);\n}\n\nfunction reorder(source) {\n source.data = filter(''orderBy'')(source.data, source.query.order);\n}\n\nfunction convertData(data) {\n var rowsMap = [];\n for (var d = 0; d < data.length; d++) {\n var columnData = data[d].data;\n for (var i = 0; i < columnData.length; i++) {\n var cellData = columnData[i];\n var timestamp = cellData[0];\n var row = rowsMap[timestamp];\n if (!row) {\n row = [];\n row[0] = timestamp;\n for (var c = 0; c < data.length; c++) {\n row[c+1] = null;\n }\n rowsMap[timestamp] = row;\n }\n row[d+1] = cellData[1];\n }\n }\n var rows = [];\n for (var t in rowsMap) {\n rows.push(rowsMap[t]);\n }\n return rows;\n}\n\nfunction updateSourceData(source) {\n source.data = convertData(source.rawData);\n source.ts.count = source.data.length;\n reorder(source);\n updatePage(source);\n}\n\nfns.redraw = function(containerElement, width, height, data,\n timeWindow, sizeChanged, scope) {\n for (var s in scope.sources) {\n var source = scope.sources[s];\n source.rawData = data.slice(source.keyStartIndex, source.keyEndIndex);\n }\n updateSourceData(scope.sources[scope.sourceIndex]);\n scope.$apply();\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\"\n ]\n}","dataKeySettingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\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, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix(''blue'', ''red'', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: ''20px'',\\n color: ''#ffffff'',\\n background: color.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor(''blue'');\\n backgroundColor.setAlpha(value/100);\\n var color = ''blue'';\\n if (value > 50) {\\n color = ''white'';\\n }\\n \\n return {\\n paddingLeft: ''20px'',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":70000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\"}"}',
'Timeseries table' );
INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
diff --git a/pom.xml b/pom.xml
index 909e255502..069c00c40b 100755
--- a/pom.xml
+++ b/pom.xml
@@ -24,12 +24,12 @@
pom
Thingsboard
- http://thingsboard.io
+ https://thingsboard.io
2016
${basedir}
- 1.4.2.RELEASE
+ 1.4.3.RELEASE
4.3.4.RELEASE
4.2.0.RELEASE
0.7.0
@@ -70,6 +70,7 @@
3.0.2
2.6.1
1.56
+ 2.0.1
@@ -127,6 +128,11 @@
maven-jar-plugin
3.0.2
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.0.0
+
org.springframework.boot
spring-boot-maven-plugin
@@ -264,6 +270,7 @@
src/font/**
src/sh/**
src/main/scripts/control/**
+ src/main/scripts/windows/**
JAVADOC_STYLE
@@ -700,6 +707,14 @@
bcpkix-jdk15on
${bouncycastle.version}
+
+ com.sun.winsw
+ winsw
+ ${winsw.version}
+ bin
+ exe
+ provided
+
diff --git a/ui/package.json b/ui/package.json
index fd69493161..365dd0c5d4 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -27,7 +27,7 @@
"angular-gridster": "^0.13.14",
"angular-hotkeys": "^1.7.0",
"angular-jwt": "^0.1.6",
- "angular-material": "^1.1.1",
+ "angular-material": "1.1.1",
"angular-material-data-table": "^0.10.9",
"angular-material-icons": "^0.7.1",
"angular-messages": "1.5.8",
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
index a519d8bac2..2da5224613 100644
--- a/ui/src/app/api/datasource.service.js
+++ b/ui/src/app/api/datasource.service.js
@@ -357,23 +357,8 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
return data;
}
- function generateSeries(dataKey) {
-
+ function generateSeries(dataKey, startTime, endTime) {
var data = [];
- var startTime;
- var endTime;
-
- if (realtime) {
- endTime = (new Date).getTime();
- if (dataKey.lastUpdateTime) {
- startTime = dataKey.lastUpdateTime + frequency;
- } else {
- startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
- }
- } else {
- startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
- endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
- }
var prevSeries;
var datasourceKeyData = datasourceData[dataKey.key];
if (datasourceKeyData.length > 0) {
@@ -429,9 +414,33 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
}
function onTick() {
- for (var key in dataKeys) {
- dataGenFunction(dataKeys[key]);
+ var key;
+ if (datasourceSubscription.type === types.widgetType.timeseries.value) {
+ var startTime;
+ var endTime;
+ for (key in dataKeys) {
+ var dataKey = dataKeys[key];
+ if (!startTime) {
+ if (realtime) {
+ endTime = (new Date).getTime();
+ if (dataKey.lastUpdateTime) {
+ startTime = dataKey.lastUpdateTime + frequency;
+ } else {
+ startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
+ }
+ } else {
+ startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
+ endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
+ }
+ }
+ generateSeries(dataKey, startTime, endTime);
+ }
+ } else if (datasourceSubscription.type === types.widgetType.latest.value) {
+ for (key in dataKeys) {
+ generateLatest(dataKeys[key]);
+ }
}
+
if (!history) {
timer = $timeout(onTick, frequency / 2, false);
}
diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js
index 99a7c80d38..0469364797 100644
--- a/ui/src/app/api/telemetry-websocket.service.js
+++ b/ui/src/app/api/telemetry-websocket.service.js
@@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
.factory('telemetryWebsocketService', TelemetryWebsocketService)
.name;
-const RECONNECT_INTERVAL = 5000;
+const RECONNECT_INTERVAL = 2000;
const WS_IDLE_TIMEOUT = 90000;
/*@ngInject*/
@@ -145,6 +145,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
}
function subscribe (subscriber) {
+ isActive = true;
var cmdId = nextCmdId();
subscribers[cmdId] = subscriber;
subscribersCount++;
@@ -163,19 +164,25 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
}
function unsubscribe (subscriber) {
- if (subscriber.subscriptionCommand) {
- subscriber.subscriptionCommand.unsubscribe = true;
- if (subscriber.type === types.dataKeyType.timeseries) {
- cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
- } else if (subscriber.type === types.dataKeyType.attribute) {
- cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
+ if (isActive) {
+ var cmdId = null;
+ if (subscriber.subscriptionCommand) {
+ subscriber.subscriptionCommand.unsubscribe = true;
+ if (subscriber.type === types.dataKeyType.timeseries) {
+ cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
+ } else if (subscriber.type === types.dataKeyType.attribute) {
+ cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
+ }
+ cmdId = subscriber.subscriptionCommand.cmdId;
+ } else if (subscriber.historyCommand) {
+ cmdId = subscriber.historyCommand.cmdId;
}
- delete subscribers[subscriber.subscriptionCommand.cmdId];
- } else if (subscriber.historyCommand) {
- delete subscribers[subscriber.historyCommand.cmdId];
+ if (cmdId && subscribers[cmdId]) {
+ delete subscribers[cmdId];
+ subscribersCount--;
+ }
+ publishCommands();
}
- subscribersCount--;
- publishCommands();
}
function checkToClose () {
@@ -187,23 +194,24 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
}
function tryOpenSocket () {
- isActive = true;
- if (!isOpened && !isOpening) {
- isOpening = true;
- if (userService.isJwtTokenValid()) {
- openSocket(userService.getJwtToken());
- } else {
- userService.refreshJwtToken().then(function success() {
+ if (isActive) {
+ if (!isOpened && !isOpening) {
+ isOpening = true;
+ if (userService.isJwtTokenValid()) {
openSocket(userService.getJwtToken());
- }, function fail() {
- isOpening = false;
- $rootScope.$broadcast('unauthenticated');
- });
+ } else {
+ userService.refreshJwtToken().then(function success() {
+ openSocket(userService.getJwtToken());
+ }, function fail() {
+ isOpening = false;
+ $rootScope.$broadcast('unauthenticated');
+ });
+ }
+ }
+ if (socketCloseTimer) {
+ $timeout.cancel(socketCloseTimer);
+ socketCloseTimer = null;
}
- }
- if (socketCloseTimer) {
- $timeout.cancel(socketCloseTimer);
- socketCloseTimer = null;
}
}
@@ -222,7 +230,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
}
}
- function reset(closeSocket) {
+ function reset(close) {
if (socketCloseTimer) {
$timeout.cancel(socketCloseTimer);
socketCloseTimer = null;
@@ -233,7 +241,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
cmdsWrapper.tsSubCmds = [];
cmdsWrapper.historyCmds = [];
cmdsWrapper.attrSubCmds = [];
- if (closeSocket) {
+ if (close) {
closeSocket();
}
}
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 2f394295ef..111f0d3f10 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -35,12 +35,10 @@
tb-mouseup="vm.widgetMouseUp($event, widget)"
ng-click=""
tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)"
- style="
- cursor: pointer;
- color: {{vm.widgetColor(widget)}};
- background-color: {{vm.widgetBackgroundColor(widget)}};
- padding: {{vm.widgetPadding(widget)}}
- ">
+ ng-style="{cursor: 'pointer',
+ color: vm.widgetColor(widget),
+ backgroundColor: vm.widgetBackgroundColor(widget),
+ padding: vm.widgetPadding(widget)}">
{{widget.config.title}}
diff --git a/ui/src/app/components/menu-toggle.tpl.html b/ui/src/app/components/menu-toggle.tpl.html
index fb7fdf2bca..454b46266e 100644
--- a/ui/src/app/components/menu-toggle.tpl.html
+++ b/ui/src/app/components/menu-toggle.tpl.html
@@ -26,7 +26,7 @@
class=" pull-right fa fa-chevron-down md-toggle-icon"
ng-class="{'tb-toggled' : sectionActive()}">
-