thingsboard-aplcache

Details

diff --git a/dao/src/main/resources/system-data.cql b/dao/src/main/resources/system-data.cql
index 5694926..374a0ba 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":"<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\"}"}',
+'{"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\"}"}',
 'Timeseries table' );
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )

ui/package.json 2(+1 -1)

diff --git a/ui/package.json b/ui/package.json
index fd69493..365dd0c 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 99a7c80..0469364 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 2f39429..111f0d3 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)}">
 								<div class="tb-widget-title" layout="column" ng-show="vm.showWidgetTitle(widget) || vm.hasTimewindow(widget)">
 									<span ng-show="vm.showWidgetTitle(widget)" class="md-subhead">{{widget.config.title}}</span>
 									<tb-timewindow ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
index a3a4390..63794dc 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) {