Commit 2c28b4f63972a18c5aa318aca761684c25ab4e59

Authored by Andrew Shvayka
2 parents 2d27b444 566bdb1d

Merge branch 'master' of github.com:thingsboard/thingsboard

... ... @@ -28,14 +28,17 @@
28 28 <packaging>jar</packaging>
29 29
30 30 <name>Thingsboard Server Application</name>
31   - <url>http://thingsboard.org</url>
  31 + <url>https://thingsboard.io</url>
  32 + <description>Open-source IoT Platform - Device management, data collection, processing and visualization
  33 + </description>
32 34
33 35 <properties>
34 36 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
35 37 <main.dir>${basedir}/..</main.dir>
36 38 <pkg.name>thingsboard</pkg.name>
37   - <pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder>
  39 + <pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
38 40 <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
  41 + <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
39 42 </properties>
40 43
41 44 <dependencies>
... ... @@ -197,6 +200,13 @@
197 200 <artifactId>springfox-swagger2</artifactId>
198 201 </dependency>
199 202 <dependency>
  203 + <groupId>com.sun.winsw</groupId>
  204 + <artifactId>winsw</artifactId>
  205 + <classifier>bin</classifier>
  206 + <type>exe</type>
  207 + <scope>provided</scope>
  208 + </dependency>
  209 + <dependency>
200 210 <groupId>org.thingsboard</groupId>
201 211 <artifactId>tools</artifactId>
202 212 <scope>test</scope>
... ... @@ -291,6 +301,38 @@
291 301 <filtering>true</filtering>
292 302 </resource>
293 303 </resources>
  304 + <filters>
  305 + <filter>src/main/filters/unix.properties</filter>
  306 + </filters>
  307 + </configuration>
  308 + </execution>
  309 + <execution>
  310 + <id>copy-win-conf</id>
  311 + <phase>process-resources</phase>
  312 + <goals>
  313 + <goal>copy-resources</goal>
  314 + </goals>
  315 + <configuration>
  316 + <outputDirectory>${pkg.win.dist}/conf</outputDirectory>
  317 + <resources>
  318 + <resource>
  319 + <directory>src/main/resources</directory>
  320 + <excludes>
  321 + <exclude>logback.xml</exclude>
  322 + </excludes>
  323 + <filtering>false</filtering>
  324 + </resource>
  325 + <resource>
  326 + <directory>src/main/conf</directory>
  327 + <excludes>
  328 + <exclude>thingsboard.conf</exclude>
  329 + </excludes>
  330 + <filtering>true</filtering>
  331 + </resource>
  332 + </resources>
  333 + <filters>
  334 + <filter>src/main/filters/windows.properties</filter>
  335 + </filters>
294 336 </configuration>
295 337 </execution>
296 338 <execution>
... ... @@ -307,6 +349,28 @@
307 349 <filtering>true</filtering>
308 350 </resource>
309 351 </resources>
  352 + <filters>
  353 + <filter>src/main/filters/unix.properties</filter>
  354 + </filters>
  355 + </configuration>
  356 + </execution>
  357 + <execution>
  358 + <id>copy-windows-control</id>
  359 + <phase>process-resources</phase>
  360 + <goals>
  361 + <goal>copy-resources</goal>
  362 + </goals>
  363 + <configuration>
  364 + <outputDirectory>${pkg.win.dist}</outputDirectory>
  365 + <resources>
  366 + <resource>
  367 + <directory>src/main/scripts/windows</directory>
  368 + <filtering>true</filtering>
  369 + </resource>
  370 + </resources>
  371 + <filters>
  372 + <filter>src/main/filters/windows.properties</filter>
  373 + </filters>
310 374 </configuration>
311 375 </execution>
312 376 <execution>
... ... @@ -361,6 +425,25 @@
361 425 </artifactItems>
362 426 </configuration>
363 427 </execution>
  428 + <execution>
  429 + <id>copy-winsw-service</id>
  430 + <phase>package</phase>
  431 + <goals>
  432 + <goal>copy</goal>
  433 + </goals>
  434 + <configuration>
  435 + <artifactItems>
  436 + <artifactItem>
  437 + <groupId>com.sun.winsw</groupId>
  438 + <artifactId>winsw</artifactId>
  439 + <classifier>bin</classifier>
  440 + <type>exe</type>
  441 + <destFileName>service.exe</destFileName>
  442 + </artifactItem>
  443 + </artifactItems>
  444 + <outputDirectory>${pkg.win.dist}</outputDirectory>
  445 + </configuration>
  446 + </execution>
364 447 </executions>
365 448 </plugin>
366 449 <plugin>
... ... @@ -385,7 +468,7 @@
385 468 <excludeDevtools>true</excludeDevtools>
386 469 <embeddedLaunchScriptProperties>
387 470 <confFolder>${pkg.installFolder}/conf</confFolder>
388   - <logFolder>${pkg.logFolder}</logFolder>
  471 + <logFolder>${pkg.unixLogFolder}</logFolder>
389 472 <logFilename>${pkg.name}.out</logFilename>
390 473 </embeddedLaunchScriptProperties>
391 474 </configuration>
... ... @@ -412,7 +495,7 @@
412 495 <arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</arg>
413 496 <arg>-PpkgName=${pkg.name}</arg>
414 497 <arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
415   - <arg>-PpkgLogFolder=${pkg.logFolder}</arg>
  498 + <arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
416 499 </args>
417 500 </configuration>
418 501 <executions>
... ... @@ -425,6 +508,25 @@
425 508 </executions>
426 509 </plugin>
427 510 <plugin>
  511 + <groupId>org.apache.maven.plugins</groupId>
  512 + <artifactId>maven-assembly-plugin</artifactId>
  513 + <configuration>
  514 + <finalName>${pkg.name}</finalName>
  515 + <descriptors>
  516 + <descriptor>src/main/assembly/windows.xml</descriptor>
  517 + </descriptors>
  518 + </configuration>
  519 + <executions>
  520 + <execution>
  521 + <id>assembly</id>
  522 + <phase>package</phase>
  523 + <goals>
  524 + <goal>single</goal>
  525 + </goals>
  526 + </execution>
  527 + </executions>
  528 + </plugin>
  529 + <plugin>
428 530 <groupId>org.xolstice.maven.plugins</groupId>
429 531 <artifactId>protobuf-maven-plugin</artifactId>
430 532 </plugin>
... ... @@ -434,4 +536,14 @@
434 536 </plugin>
435 537 </plugins>
436 538 </build>
  539 + <repositories>
  540 + <repository>
  541 + <id>jenkins</id>
  542 + <name>Jenkins Repository</name>
  543 + <url>http://repo.jenkins-ci.org/releases</url>
  544 + <snapshots>
  545 + <enabled>false</enabled>
  546 + </snapshots>
  547 + </repository>
  548 + </repositories>
437 549 </project>
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
  19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  20 + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
  21 + <id>windows</id>
  22 +
  23 + <formats>
  24 + <format>zip</format>
  25 + </formats>
  26 +
  27 + <!-- Workaround to create logs directory -->
  28 + <fileSets>
  29 + <fileSet>
  30 + <directory>${pkg.win.dist}</directory>
  31 + <outputDirectory>logs</outputDirectory>
  32 + <excludes>
  33 + <exclude>*/**</exclude>
  34 + </excludes>
  35 + </fileSet>
  36 + <fileSet>
  37 + <directory>${pkg.win.dist}/conf</directory>
  38 + <outputDirectory>conf</outputDirectory>
  39 + <lineEnding>windows</lineEnding>
  40 + </fileSet>
  41 + <fileSet>
  42 + <directory>${project.build.directory}/extensions</directory>
  43 + <outputDirectory>extensions</outputDirectory>
  44 + </fileSet>
  45 + <fileSet>
  46 + <directory>${project.build.directory}/data</directory>
  47 + <outputDirectory>data</outputDirectory>
  48 + </fileSet>
  49 + </fileSets>
  50 +
  51 + <files>
  52 + <file>
  53 + <source>${project.build.directory}/${project.build.finalName}-boot.${project.packaging}</source>
  54 + <outputDirectory>lib</outputDirectory>
  55 + <destName>${pkg.name}.jar</destName>
  56 + </file>
  57 + <file>
  58 + <source>${pkg.win.dist}/service.exe</source>
  59 + <outputDirectory/>
  60 + <destName>${pkg.name}.exe</destName>
  61 + </file>
  62 + <file>
  63 + <source>${pkg.win.dist}/service.xml</source>
  64 + <outputDirectory/>
  65 + <destName>${pkg.name}.xml</destName>
  66 + <lineEnding>windows</lineEnding>
  67 + </file>
  68 + <file>
  69 + <source>${pkg.win.dist}/install.bat</source>
  70 + <outputDirectory/>
  71 + <lineEnding>windows</lineEnding>
  72 + </file>
  73 + <file>
  74 + <source>${pkg.win.dist}/uninstall.bat</source>
  75 + <outputDirectory/>
  76 + <lineEnding>windows</lineEnding>
  77 + </file>
  78 + </files>
  79 +</assembly>
... ...
  1 +pkg.logFolder=${pkg.unixLogFolder}
\ No newline at end of file
... ...
  1 +pkg.logFolder=${BASE}\\logs
  2 +pkg.winWrapperLogFolder=%BASE%\\logs
... ...
  1 +@ECHO OFF
  2 +
  3 +setlocal ENABLEEXTENSIONS
  4 +
  5 +IF %PROCESSOR_ARCHITECTURE%==AMD64 GOTO CHECK_JAVA_64
  6 +IF %PROCESSOR_ARCHITECTURE%==x86 GOTO CHECK_JAVA_32
  7 +
  8 +@ECHO Detecting Java version installed.
  9 +:CHECK_JAVA_64
  10 +@ECHO Detecting if it is 64 bit machine
  11 +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\Wow6432Node\JavaSoft\Java Runtime Environment"
  12 +set VALUE_NAME=CurrentVersion
  13 +
  14 +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
  15 + set ValueName=%%A
  16 + set ValueType=%%B
  17 + set ValueValue=%%C
  18 +)
  19 +@ECHO CurrentVersion %ValueValue%
  20 +
  21 +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
  22 +SET VALUE_NAME=JavaHome
  23 +
  24 +if defined ValueName (
  25 + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
  26 + set ValueName2=%%A
  27 + set ValueType2=%%B
  28 + set JRE_PATH2=%%C
  29 +
  30 + if defined ValueName2 (
  31 + set ValueName = %ValueName2%
  32 + set ValueType = %ValueType2%
  33 + set ValueValue = %JRE_PATH2%
  34 + )
  35 + )
  36 +)
  37 +
  38 +IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
  39 +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED
  40 +
  41 +:CHECK_JAVA_32
  42 +@ECHO Detecting if it is 32 bit machine
  43 +set KEY_NAME="HKEY_LOCAL_MACHINE\Software\JavaSoft\Java Runtime Environment"
  44 +set VALUE_NAME=CurrentVersion
  45 +
  46 +FOR /F "usebackq skip=2 tokens=1-3" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
  47 + set ValueName=%%A
  48 + set ValueType=%%B
  49 + set ValueValue=%%C
  50 +)
  51 +@ECHO CurrentVersion %ValueValue%
  52 +
  53 +SET KEY_NAME="%KEY_NAME:~1,-1%\%ValueValue%"
  54 +SET VALUE_NAME=JavaHome
  55 +
  56 +if defined ValueName (
  57 + FOR /F "usebackq skip=2 tokens=1,2*" %%A IN (`REG QUERY %KEY_NAME% /v %VALUE_NAME% 2^>nul`) DO (
  58 + set ValueName2=%%A
  59 + set ValueType2=%%B
  60 + set JRE_PATH2=%%C
  61 +
  62 + if defined ValueName2 (
  63 + set ValueName = %ValueName2%
  64 + set ValueType = %ValueType2%
  65 + set ValueValue = %JRE_PATH2%
  66 + )
  67 + )
  68 +)
  69 +
  70 +IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED
  71 +
  72 +:JAVA_INSTALLED
  73 +
  74 +@ECHO Java 1.8 found!
  75 +@ECHO Installing ${pkg.name} ...
  76 +${pkg.name}.exe install
  77 +
  78 +@ECHO DONE.
  79 +
  80 +GOTO END
  81 +
  82 +:JAVA_NOT_INSTALLED
  83 +@ECHO Java 1.8 or above is not installed
  84 +@ECHO Please go to https://java.com/ and install Java. Then retry installation.
  85 +PAUSE
  86 +GOTO END
  87 +
  88 +:END
  89 +
  90 +
... ...
  1 +<service>
  2 + <id>${pkg.name}</id>
  3 + <name>${project.name}</name>
  4 + <description>${project.description}</description>
  5 + <workingdirectory>%BASE%\conf</workingdirectory>
  6 + <logpath>${pkg.winWrapperLogFolder}</logpath>
  7 + <logmode>rotate</logmode>
  8 + <env name="LOADER_PATH" value="%BASE%\conf,%BASE%\extensions" />
  9 + <executable>java</executable>
  10 + <startargument>-jar</startargument>
  11 + <startargument>%BASE%\lib\${pkg.name}.jar</startargument>
  12 +</service>
... ...
  1 +@ECHO OFF
  2 +
  3 +@ECHO Stopping ${pkg.name} ...
  4 +net stop ${pkg.name}
  5 +
  6 +@ECHO Uninstalling ${pkg.name} ...
  7 +${pkg.name}.exe uninstall
  8 +
  9 +@ECHO DONE.
\ No newline at end of file
... ...
... ... @@ -79,7 +79,7 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'label_widget',
79 79
80 80 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
81 81 VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table',
82   -'{"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\"}"}',
  82 +'{"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\"}"}',
83 83 'Timeseries table' );
84 84
85 85 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
... ...
... ... @@ -24,12 +24,12 @@
24 24 <packaging>pom</packaging>
25 25
26 26 <name>Thingsboard</name>
27   - <url>http://thingsboard.io</url>
  27 + <url>https://thingsboard.io</url>
28 28 <inceptionYear>2016</inceptionYear>
29 29
30 30 <properties>
31 31 <main.dir>${basedir}</main.dir>
32   - <spring-boot.version>1.4.2.RELEASE</spring-boot.version>
  32 + <spring-boot.version>1.4.3.RELEASE</spring-boot.version>
33 33 <spring.version>4.3.4.RELEASE</spring.version>
34 34 <spring-security.version>4.2.0.RELEASE</spring-security.version>
35 35 <jjwt.version>0.7.0</jjwt.version>
... ... @@ -70,6 +70,7 @@
70 70 <jar-plugin.version>3.0.2</jar-plugin.version>
71 71 <springfox-swagger.version>2.6.1</springfox-swagger.version>
72 72 <bouncycastle.version>1.56</bouncycastle.version>
  73 + <winsw.version>2.0.1</winsw.version>
73 74 </properties>
74 75
75 76 <modules>
... ... @@ -128,6 +129,11 @@
128 129 <version>3.0.2</version>
129 130 </plugin>
130 131 <plugin>
  132 + <groupId>org.apache.maven.plugins</groupId>
  133 + <artifactId>maven-assembly-plugin</artifactId>
  134 + <version>3.0.0</version>
  135 + </plugin>
  136 + <plugin>
131 137 <groupId>org.springframework.boot</groupId>
132 138 <artifactId>spring-boot-maven-plugin</artifactId>
133 139 <version>${spring-boot.version}</version>
... ... @@ -264,6 +270,7 @@
264 270 <exclude>src/font/**</exclude>
265 271 <exclude>src/sh/**</exclude>
266 272 <exclude>src/main/scripts/control/**</exclude>
  273 + <exclude>src/main/scripts/windows/**</exclude>
267 274 </excludes>
268 275 <mapping>
269 276 <proto>JAVADOC_STYLE</proto>
... ... @@ -700,6 +707,14 @@
700 707 <artifactId>bcpkix-jdk15on</artifactId>
701 708 <version>${bouncycastle.version}</version>
702 709 </dependency>
  710 + <dependency>
  711 + <groupId>com.sun.winsw</groupId>
  712 + <artifactId>winsw</artifactId>
  713 + <version>${winsw.version}</version>
  714 + <classifier>bin</classifier>
  715 + <type>exe</type>
  716 + <scope>provided</scope>
  717 + </dependency>
703 718 </dependencies>
704 719 </dependencyManagement>
705 720
... ...
... ... @@ -27,7 +27,7 @@
27 27 "angular-gridster": "^0.13.14",
28 28 "angular-hotkeys": "^1.7.0",
29 29 "angular-jwt": "^0.1.6",
30   - "angular-material": "^1.1.1",
  30 + "angular-material": "1.1.1",
31 31 "angular-material-data-table": "^0.10.9",
32 32 "angular-material-icons": "^0.7.1",
33 33 "angular-messages": "1.5.8",
... ...
... ... @@ -357,23 +357,8 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
357 357 return data;
358 358 }
359 359
360   - function generateSeries(dataKey) {
361   -
  360 + function generateSeries(dataKey, startTime, endTime) {
362 361 var data = [];
363   - var startTime;
364   - var endTime;
365   -
366   - if (realtime) {
367   - endTime = (new Date).getTime();
368   - if (dataKey.lastUpdateTime) {
369   - startTime = dataKey.lastUpdateTime + frequency;
370   - } else {
371   - startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
372   - }
373   - } else {
374   - startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
375   - endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
376   - }
377 362 var prevSeries;
378 363 var datasourceKeyData = datasourceData[dataKey.key];
379 364 if (datasourceKeyData.length > 0) {
... ... @@ -429,9 +414,33 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
429 414 }
430 415
431 416 function onTick() {
432   - for (var key in dataKeys) {
433   - dataGenFunction(dataKeys[key]);
  417 + var key;
  418 + if (datasourceSubscription.type === types.widgetType.timeseries.value) {
  419 + var startTime;
  420 + var endTime;
  421 + for (key in dataKeys) {
  422 + var dataKey = dataKeys[key];
  423 + if (!startTime) {
  424 + if (realtime) {
  425 + endTime = (new Date).getTime();
  426 + if (dataKey.lastUpdateTime) {
  427 + startTime = dataKey.lastUpdateTime + frequency;
  428 + } else {
  429 + startTime = endTime - datasourceSubscription.subscriptionTimewindow.realtimeWindowMs;
  430 + }
  431 + } else {
  432 + startTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.startTimeMs;
  433 + endTime = datasourceSubscription.subscriptionTimewindow.fixedWindow.endTimeMs;
  434 + }
  435 + }
  436 + generateSeries(dataKey, startTime, endTime);
  437 + }
  438 + } else if (datasourceSubscription.type === types.widgetType.latest.value) {
  439 + for (key in dataKeys) {
  440 + generateLatest(dataKeys[key]);
  441 + }
434 442 }
  443 +
435 444 if (!history) {
436 445 timer = $timeout(onTick, frequency / 2, false);
437 446 }
... ...
... ... @@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
20 20 .factory('telemetryWebsocketService', TelemetryWebsocketService)
21 21 .name;
22 22
23   -const RECONNECT_INTERVAL = 5000;
  23 +const RECONNECT_INTERVAL = 2000;
24 24 const WS_IDLE_TIMEOUT = 90000;
25 25
26 26 /*@ngInject*/
... ... @@ -145,6 +145,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
145 145 }
146 146
147 147 function subscribe (subscriber) {
  148 + isActive = true;
148 149 var cmdId = nextCmdId();
149 150 subscribers[cmdId] = subscriber;
150 151 subscribersCount++;
... ... @@ -163,19 +164,25 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
163 164 }
164 165
165 166 function unsubscribe (subscriber) {
166   - if (subscriber.subscriptionCommand) {
167   - subscriber.subscriptionCommand.unsubscribe = true;
168   - if (subscriber.type === types.dataKeyType.timeseries) {
169   - cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
170   - } else if (subscriber.type === types.dataKeyType.attribute) {
171   - cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
  167 + if (isActive) {
  168 + var cmdId = null;
  169 + if (subscriber.subscriptionCommand) {
  170 + subscriber.subscriptionCommand.unsubscribe = true;
  171 + if (subscriber.type === types.dataKeyType.timeseries) {
  172 + cmdsWrapper.tsSubCmds.push(subscriber.subscriptionCommand);
  173 + } else if (subscriber.type === types.dataKeyType.attribute) {
  174 + cmdsWrapper.attrSubCmds.push(subscriber.subscriptionCommand);
  175 + }
  176 + cmdId = subscriber.subscriptionCommand.cmdId;
  177 + } else if (subscriber.historyCommand) {
  178 + cmdId = subscriber.historyCommand.cmdId;
172 179 }
173   - delete subscribers[subscriber.subscriptionCommand.cmdId];
174   - } else if (subscriber.historyCommand) {
175   - delete subscribers[subscriber.historyCommand.cmdId];
  180 + if (cmdId && subscribers[cmdId]) {
  181 + delete subscribers[cmdId];
  182 + subscribersCount--;
  183 + }
  184 + publishCommands();
176 185 }
177   - subscribersCount--;
178   - publishCommands();
179 186 }
180 187
181 188 function checkToClose () {
... ... @@ -187,23 +194,24 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
187 194 }
188 195
189 196 function tryOpenSocket () {
190   - isActive = true;
191   - if (!isOpened && !isOpening) {
192   - isOpening = true;
193   - if (userService.isJwtTokenValid()) {
194   - openSocket(userService.getJwtToken());
195   - } else {
196   - userService.refreshJwtToken().then(function success() {
  197 + if (isActive) {
  198 + if (!isOpened && !isOpening) {
  199 + isOpening = true;
  200 + if (userService.isJwtTokenValid()) {
197 201 openSocket(userService.getJwtToken());
198   - }, function fail() {
199   - isOpening = false;
200   - $rootScope.$broadcast('unauthenticated');
201   - });
  202 + } else {
  203 + userService.refreshJwtToken().then(function success() {
  204 + openSocket(userService.getJwtToken());
  205 + }, function fail() {
  206 + isOpening = false;
  207 + $rootScope.$broadcast('unauthenticated');
  208 + });
  209 + }
  210 + }
  211 + if (socketCloseTimer) {
  212 + $timeout.cancel(socketCloseTimer);
  213 + socketCloseTimer = null;
202 214 }
203   - }
204   - if (socketCloseTimer) {
205   - $timeout.cancel(socketCloseTimer);
206   - socketCloseTimer = null;
207 215 }
208 216 }
209 217
... ... @@ -222,7 +230,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
222 230 }
223 231 }
224 232
225   - function reset(closeSocket) {
  233 + function reset(close) {
226 234 if (socketCloseTimer) {
227 235 $timeout.cancel(socketCloseTimer);
228 236 socketCloseTimer = null;
... ... @@ -233,7 +241,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
233 241 cmdsWrapper.tsSubCmds = [];
234 242 cmdsWrapper.historyCmds = [];
235 243 cmdsWrapper.attrSubCmds = [];
236   - if (closeSocket) {
  244 + if (close) {
237 245 closeSocket();
238 246 }
239 247 }
... ...
... ... @@ -35,12 +35,10 @@
35 35 tb-mouseup="vm.widgetMouseUp($event, widget)"
36 36 ng-click=""
37 37 tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)"
38   - style="
39   - cursor: pointer;
40   - color: {{vm.widgetColor(widget)}};
41   - background-color: {{vm.widgetBackgroundColor(widget)}};
42   - padding: {{vm.widgetPadding(widget)}}
43   - ">
  38 + ng-style="{cursor: 'pointer',
  39 + color: vm.widgetColor(widget),
  40 + backgroundColor: vm.widgetBackgroundColor(widget),
  41 + padding: vm.widgetPadding(widget)}">
44 42 <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
45 43 <span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
46 44 <tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
... ...
... ... @@ -26,7 +26,7 @@
26 26 class=" pull-right fa fa-chevron-down md-toggle-icon"
27 27 ng-class="{'tb-toggled' : sectionActive()}"></span>
28 28 </md-button>
29   -<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" style="height: {{sectionHeight()}};">
  29 +<ul id="docs-menu-{{section.name | nospace}}" class="tb-menu-toggle-list" ng-style="{height: sectionHeight()}">
30 30 <li ng-repeat="page in section.pages">
31 31 <tb-menu-link section="page"></tb-menu-link>
32 32 </li>
... ...
... ... @@ -119,6 +119,10 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
119 119 scope.attributesDeferred.resolve();
120 120 }
121 121 if (scope.deviceId && scope.attributeScope) {
  122 + scope.attributes = {
  123 + count: 0,
  124 + data: []
  125 + };
122 126 scope.checkSubscription();
123 127 scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
124 128 scope.query, function(attributes, update) {
... ...