Commit 0ffac9c20b41fe25c9413ea65ad70a7d94f0b537

Authored by Igor Kulikov
1 parent 7c6cdb14

UI: Minor fixed. Improve Timeseries table widget.

... ... @@ -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 //{{ $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\"}"}',
83 83 'Timeseries table' );
84 84
85 85 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
... ...
... ... @@ -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",
... ...
... ... @@ -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>
... ...
... ... @@ -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) {
... ...