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,7 +79,7 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'label_widget',
79 79
80 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" ) 80 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
81 VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table', 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 'Timeseries table' ); 83 'Timeseries table' );
84 84
85 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" ) 85 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
@@ -27,7 +27,7 @@ @@ -27,7 +27,7 @@
27 "angular-gridster": "^0.13.14", 27 "angular-gridster": "^0.13.14",
28 "angular-hotkeys": "^1.7.0", 28 "angular-hotkeys": "^1.7.0",
29 "angular-jwt": "^0.1.6", 29 "angular-jwt": "^0.1.6",
30 - "angular-material": "^1.1.1", 30 + "angular-material": "1.1.1",
31 "angular-material-data-table": "^0.10.9", 31 "angular-material-data-table": "^0.10.9",
32 "angular-material-icons": "^0.7.1", 32 "angular-material-icons": "^0.7.1",
33 "angular-messages": "1.5.8", 33 "angular-messages": "1.5.8",
@@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard @@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard
20 .factory('telemetryWebsocketService', TelemetryWebsocketService) 20 .factory('telemetryWebsocketService', TelemetryWebsocketService)
21 .name; 21 .name;
22 22
23 -const RECONNECT_INTERVAL = 5000; 23 +const RECONNECT_INTERVAL = 2000;
24 const WS_IDLE_TIMEOUT = 90000; 24 const WS_IDLE_TIMEOUT = 90000;
25 25
26 /*@ngInject*/ 26 /*@ngInject*/
@@ -145,6 +145,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty @@ -145,6 +145,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
145 } 145 }
146 146
147 function subscribe (subscriber) { 147 function subscribe (subscriber) {
  148 + isActive = true;
148 var cmdId = nextCmdId(); 149 var cmdId = nextCmdId();
149 subscribers[cmdId] = subscriber; 150 subscribers[cmdId] = subscriber;
150 subscribersCount++; 151 subscribersCount++;
@@ -163,19 +164,25 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty @@ -163,19 +164,25 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
163 } 164 }
164 165
165 function unsubscribe (subscriber) { 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 function checkToClose () { 188 function checkToClose () {
@@ -187,23 +194,24 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty @@ -187,23 +194,24 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
187 } 194 }
188 195
189 function tryOpenSocket () { 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 openSocket(userService.getJwtToken()); 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,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 if (socketCloseTimer) { 234 if (socketCloseTimer) {
227 $timeout.cancel(socketCloseTimer); 235 $timeout.cancel(socketCloseTimer);
228 socketCloseTimer = null; 236 socketCloseTimer = null;
@@ -233,7 +241,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty @@ -233,7 +241,7 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
233 cmdsWrapper.tsSubCmds = []; 241 cmdsWrapper.tsSubCmds = [];
234 cmdsWrapper.historyCmds = []; 242 cmdsWrapper.historyCmds = [];
235 cmdsWrapper.attrSubCmds = []; 243 cmdsWrapper.attrSubCmds = [];
236 - if (closeSocket) { 244 + if (close) {
237 closeSocket(); 245 closeSocket();
238 } 246 }
239 } 247 }
@@ -35,12 +35,10 @@ @@ -35,12 +35,10 @@
35 tb-mouseup="vm.widgetMouseUp($event, widget)" 35 tb-mouseup="vm.widgetMouseUp($event, widget)"
36 ng-click="" 36 ng-click=""
37 tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)" 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 <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)"> 42 <div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
45 <span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span> 43 <span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
46 <tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> 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,6 +119,10 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
119 scope.attributesDeferred.resolve(); 119 scope.attributesDeferred.resolve();
120 } 120 }
121 if (scope.deviceId && scope.attributeScope) { 121 if (scope.deviceId && scope.attributeScope) {
  122 + scope.attributes = {
  123 + count: 0,
  124 + data: []
  125 + };
122 scope.checkSubscription(); 126 scope.checkSubscription();
123 scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value, 127 scope.attributesDeferred = deviceService.getDeviceAttributes(scope.deviceId, scope.attributeScope.value,
124 scope.query, function(attributes, update) { 128 scope.query, function(attributes, update) {