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