Commit 2c28b4f63972a18c5aa318aca761684c25ab4e59
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
15 changed files
with
401 additions
and
62 deletions
... | ... | @@ -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> | ... | ... |
application/src/main/assembly/windows.xml
0 → 100644
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> | ... | ... |
application/src/main/filters/unix.properties
0 → 100644
1 | +pkg.logFolder=${pkg.unixLogFolder} | |
\ No newline at end of file | ... | ... |
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> | ... | ... |
... | ... | @@ -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) { | ... | ... |