thingsboard-aplcache
Changes
application/pom.xml 120(+116 -4)
application/src/main/assembly/windows.xml 79(+79 -0)
pom.xml 19(+17 -2)
ui/package.json 2(+1 -1)
ui/src/app/api/datasource.service.js 45(+27 -18)
ui/src/app/api/telemetry-websocket.service.js 66(+37 -29)
ui/src/app/components/dashboard.tpl.html 10(+4 -6)
Details
application/pom.xml 120(+116 -4)
diff --git a/application/pom.xml b/application/pom.xml
index d06fbfa..6efb019 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -28,14 +28,17 @@
<packaging>jar</packaging>
<name>Thingsboard Server Application</name>
- <url>http://thingsboard.org</url>
+ <url>https://thingsboard.io</url>
+ <description>Open-source IoT Platform - Device management, data collection, processing and visualization
+ </description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/..</main.dir>
<pkg.name>thingsboard</pkg.name>
- <pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
+ <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+ <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
</properties>
<dependencies>
@@ -197,6 +200,13 @@
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
+ <groupId>com.sun.winsw</groupId>
+ <artifactId>winsw</artifactId>
+ <classifier>bin</classifier>
+ <type>exe</type>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.thingsboard</groupId>
<artifactId>tools</artifactId>
<scope>test</scope>
@@ -291,6 +301,38 @@
<filtering>true</filtering>
</resource>
</resources>
+ <filters>
+ <filter>src/main/filters/unix.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-win-conf</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/resources</directory>
+ <excludes>
+ <exclude>logback.xml</exclude>
+ </excludes>
+ <filtering>false</filtering>
+ </resource>
+ <resource>
+ <directory>src/main/conf</directory>
+ <excludes>
+ <exclude>thingsboard.conf</exclude>
+ </excludes>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/windows.properties</filter>
+ </filters>
</configuration>
</execution>
<execution>
@@ -307,6 +349,28 @@
<filtering>true</filtering>
</resource>
</resources>
+ <filters>
+ <filter>src/main/filters/unix.properties</filter>
+ </filters>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-windows-control</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${pkg.win.dist}</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/scripts/windows</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ <filters>
+ <filter>src/main/filters/windows.properties</filter>
+ </filters>
</configuration>
</execution>
<execution>
@@ -361,6 +425,25 @@
</artifactItems>
</configuration>
</execution>
+ <execution>
+ <id>copy-winsw-service</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>com.sun.winsw</groupId>
+ <artifactId>winsw</artifactId>
+ <classifier>bin</classifier>
+ <type>exe</type>
+ <destFileName>service.exe</destFileName>
+ </artifactItem>
+ </artifactItems>
+ <outputDirectory>${pkg.win.dist}</outputDirectory>
+ </configuration>
+ </execution>
</executions>
</plugin>
<plugin>
@@ -385,7 +468,7 @@
<excludeDevtools>true</excludeDevtools>
<embeddedLaunchScriptProperties>
<confFolder>${pkg.installFolder}/conf</confFolder>
- <logFolder>${pkg.logFolder}</logFolder>
+ <logFolder>${pkg.unixLogFolder}</logFolder>
<logFilename>${pkg.name}.out</logFilename>
</embeddedLaunchScriptProperties>
</configuration>
@@ -412,7 +495,7 @@
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
<arg>-PpkgName=${pkg.name}</arg>
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
- <arg>-PpkgLogFolder=${pkg.logFolder}</arg>
+ <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
</args>
</configuration>
<executions>
@@ -425,6 +508,25 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <configuration>
+ <finalName>${pkg.name}</finalName>
+ <descriptors>
+ <descriptor>src/main/assembly/windows.xml</descriptor>
+ </descriptors>
+ </configuration>
+ <executions>
+ <execution>
+ <id>assembly</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
@@ -434,4 +536,14 @@
</plugin>
</plugins>
</build>
+ <repositories>
+ <repository>
+ <id>jenkins</id>
+ <name>Jenkins Repository</name>
+ <url>http://repo.jenkins-ci.org/releases</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
</project>
application/src/main/assembly/windows.xml 79(+79 -0)
diff --git a/application/src/main/assembly/windows.xml b/application/src/main/assembly/windows.xml
new file mode 100644
index 0000000..023c435
--- /dev/null
+++ b/application/src/main/assembly/windows.xml
@@ -0,0 +1,79 @@
+<!--
+
+ Copyright © 2016-2017 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.
+
+-->
+<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
+ <id>windows</id>
+
+ <formats>
+ <format>zip</format>
+ </formats>
+
+ <!-- Workaround to create logs directory -->
+ <fileSets>
+ <fileSet>
+ <directory>${pkg.win.dist}</directory>
+ <outputDirectory>logs</outputDirectory>
+ <excludes>
+ <exclude>*/**</exclude>
+ </excludes>
+ </fileSet>
+ <fileSet>
+ <directory>${pkg.win.dist}/conf</directory>
+ <outputDirectory>conf</outputDirectory>
+ <lineEnding>windows</lineEnding>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/extensions</directory>
+ <outputDirectory>extensions</outputDirectory>
+ </fileSet>
+ <fileSet>
+ <directory>${project.build.directory}/data</directory>
+ <outputDirectory>data</outputDirectory>
+ </fileSet>
+ </fileSets>
+
+ <files>
+ <file>
+ <source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source>
+ <outputDirectory>lib</outputDirectory>
+ <destName>${pkg.name}.jar</destName>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/service.exe</source>
+ <outputDirectory/>
+ <destName>${pkg.name}.exe</destName>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/service.xml</source>
+ <outputDirectory/>
+ <destName>${pkg.name}.xml</destName>
+ <lineEnding>windows</lineEnding>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/install.bat</source>
+ <outputDirectory/>
+ <lineEnding>windows</lineEnding>
+ </file>
+ <file>
+ <source>${pkg.win.dist}/uninstall.bat</source>
+ <outputDirectory/>
+ <lineEnding>windows</lineEnding>
+ </file>
+ </files>
+</assembly>
diff --git a/application/src/main/filters/unix.properties b/application/src/main/filters/unix.properties
new file mode 100644
index 0000000..8967278
--- /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 0000000..a6e48d9
--- /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 0000000..97c4feb
--- /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 0000000..b2acc45
--- /dev/null
+++ b/application/src/main/scripts/windows/service.xml
@@ -0,0 +1,12 @@
+<service>
+ <id>${pkg.name}</id>
+ <name>${project.name}</name>
+ <description>${project.description}</description>
+ <workingdirectory>%BASE%\conf</workingdirectory>
+ <logpath>${pkg.winWrapperLogFolder}</logpath>
+ <logmode>rotate</logmode>
+ <env name="LOADER_PATH" value="%BASE%\conf,%BASE%\extensions" />
+ <executable>java</executable>
+ <startargument>-jar</startargument>
+ <startargument>%BASE%\lib\${pkg.name}.jar</startargument>
+</service>
diff --git a/application/src/main/scripts/windows/uninstall.bat b/application/src/main/scripts/windows/uninstall.bat
new file mode 100644
index 0000000..d07cf87
--- /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 5694926..a687a4e 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":"<md-tabs md-selected=\"sourceIndex\" ng-class=\"{''tb-headless'': sources.length === 1}\"\n id=\"tabs\" md-border-bottom flex class=\"tb-absolute-fill\">\n <md-tab ng-repeat=\"source in sources\" label=\"{{ source.label }}\">\n <md-table-container>\n <table md-table>\n <thead md-head md-order=\"source.query.order\" md-on-reorder=\"onReorder(source)\">\n <tr md-row>\n <th md-column md-order-by=\"0\"><span>Timestamp</span></th>\n <th md-column md-order-by=\"{{ h.index }}\" ng-repeat=\"h in source.ts.header\"><span>{{ h.label }}</span></th>\n </tr>\n </thead>\n <tbody md-body>\n <tr md-row ng-repeat=\"row in source.ts.data\">\n <td md-cell ng-repeat=\"d in row track by $index\" ng-style=\"cellStyle(source, $index, d)\">\n {{ $index === 0 ? (d | date : ''yyyy-MM-dd HH:mm:ss'') : d }}\n </td>\n </tr> \n </tbody> \n </table>\n </md-table-container>\n <md-table-pagination md-limit=\"source.query.limit\" md-limit-options=\"[5, 10, 15]\"\n md-page=\"source.query.page\" md-total=\"{{source.ts.count}}\"\n md-on-paginate=\"onPaginate(source)\" md-page-select>\n </md-table-pagination>\n </md-tab>\n</md-tabs>","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":"<md-tabs md-selected=\"sourceIndex\" ng-class=\"{''tb-headless'': sources.length === 1}\"\n id=\"tabs\" md-border-bottom flex class=\"tb-absolute-fill\">\n <md-tab ng-repeat=\"source in sources\" label=\"{{ source.label }}\">\n <md-table-container>\n <table md-table>\n <thead md-head md-order=\"source.query.order\" md-on-reorder=\"onReorder(source)\">\n <tr md-row>\n <th ng-show=\"showTimestamp\" md-column md-order-by=\"0\"><span>Timestamp</span></th>\n <th md-column md-order-by=\"{{ h.index }}\" ng-repeat=\"h in source.ts.header\"><span>{{ h.label }}</span></th>\n </tr>\n </thead>\n <tbody md-body>\n <tr md-row ng-repeat=\"row in source.ts.data\">\n <td ng-show=\"$index > 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 </td>\n </tr> \n </tbody> \n </table>\n </md-table-container>\n <md-table-pagination md-limit=\"source.query.limit\" md-limit-options=\"[5, 10, 15]\"\n md-page=\"source.query.page\" md-total=\"{{source.ts.count}}\"\n md-on-paginate=\"onPaginate(source)\" md-page-select>\n </md-table-pagination>\n </md-tab>\n</md-tabs>","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" )
pom.xml 19(+17 -2)
diff --git a/pom.xml b/pom.xml
index 909e255..069c00c 100755
--- a/pom.xml
+++ b/pom.xml
@@ -24,12 +24,12 @@
<packaging>pom</packaging>
<name>Thingsboard</name>
- <url>http://thingsboard.io</url>
+ <url>https://thingsboard.io</url>
<inceptionYear>2016</inceptionYear>
<properties>
<main.dir>${basedir}</main.dir>
- <spring-boot.version>1.4.2.RELEASE</spring-boot.version>
+ <spring-boot.version>1.4.3.RELEASE</spring-boot.version>
<spring.version>4.3.4.RELEASE</spring.version>
<spring-security.version>4.2.0.RELEASE</spring-security.version>
<jjwt.version>0.7.0</jjwt.version>
@@ -70,6 +70,7 @@
<jar-plugin.version>3.0.2</jar-plugin.version>
<springfox-swagger.version>2.6.1</springfox-swagger.version>
<bouncycastle.version>1.56</bouncycastle.version>
+ <winsw.version>2.0.1</winsw.version>
</properties>
<modules>
@@ -128,6 +129,11 @@
<version>3.0.2</version>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <version>3.0.0</version>
+ </plugin>
+ <plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
@@ -264,6 +270,7 @@
<exclude>src/font/**</exclude>
<exclude>src/sh/**</exclude>
<exclude>src/main/scripts/control/**</exclude>
+ <exclude>src/main/scripts/windows/**</exclude>
</excludes>
<mapping>
<proto>JAVADOC_STYLE</proto>
@@ -700,6 +707,14 @@
<artifactId>bcpkix-jdk15on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
+ <dependency>
+ <groupId>com.sun.winsw</groupId>
+ <artifactId>winsw</artifactId>
+ <version>${winsw.version}</version>
+ <classifier>bin</classifier>
+ <type>exe</type>
+ <scope>provided</scope>
+ </dependency>
</dependencies>
</dependencyManagement>
ui/package.json 2(+1 -1)
diff --git a/ui/package.json b/ui/package.json
index fd69493..365dd0c 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",
ui/src/app/api/datasource.service.js 45(+27 -18)
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
index a519d8b..2da5224 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);
}
ui/src/app/api/telemetry-websocket.service.js 66(+37 -29)
diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js
index 99a7c80..0469364 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();
}
}
ui/src/app/components/dashboard.tpl.html 10(+4 -6)
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 2f39429..111f0d3 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)}">
<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
diff --git a/ui/src/app/components/menu-toggle.tpl.html b/ui/src/app/components/menu-toggle.tpl.html
index fb7fdf2..454b462 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()}"></span>
</md-button>
-<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" style="height: {{sectionHeight()}};">
+<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" ng-style="{height: sectionHeight()}">
<li ng-repeat="page in section.pages">
<tb-menu-link section="page"></tb-menu-link>
</li>
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
index a3a4390..63794dc 100644
--- a/ui/src/app/device/attribute/attribute-table.directive.js
+++ b/ui/src/app/device/attribute/attribute-table.directive.js
@@ -119,6 +119,10 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.attributesDeferred.resolve();
}
if (scope.deviceId && scope.attributeScope) {
+ scope.attributes = {
+ count: 0,
+ data: []
+ };
scope.checkSubscription();
scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
scope.query, function(attributes, update) {