thingsboard-developers

Changes

ui/.stylelintrc 6(+3 -3)

ui/package.json 16(+15 -1)

ui/src/scss/main.scss 28(+11 -17)

Details

diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 803f1d3..1ccfdc9 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -15,7 +15,7 @@
         "resources": [],
         "templateHtml": "",
         "templateCss": "#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    margin: 5px;\n    padding: 8px;\n}\n\n.tbDatasource-title {\n    font-size: 1.200rem;\n    font-weight: 500;\n    padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    box-shadow: 0 0 10px #ccc;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-size: 1.000rem;\n    color: #757575;\n}\n\n.tbDatasource-table td {\n    position: relative;\n    border-top: 1px solid rgba(0, 0, 0, 0.12);\n    border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n    padding: 0px 18px;\n    box-sizing: border-box;\n}",
-        "controllerScript": "self.onInit = function() {\n    \n    self.ctx.datasourceTitleCells = [];\n    self.ctx.valueCells = [];\n    self.ctx.labelCells = [];\n    \n    for (var i=0; i < self.ctx.datasources.length; i++) {\n        var tbDatasource = self.ctx.datasources[i];\n\n        var datasourceId = 'tbDatasource' + i;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n\n        var datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n\n        datasourceContainer.append(\n            \"<div class='tbDatasource-title'>\" +\n            tbDatasource.name + \"</div>\"\n        );\n        \n        var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n        self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n        \n        var tableId = 'table' + i;\n        datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n\n        for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n            var dataKey = tbDatasource.dataKeys[a];\n            var labelCellId = 'labelCell' + a;\n            var cellId = 'cell' + a;\n            table.append(\"<tr><td id='\" + labelCellId + \"'>\" + dataKey.label +\n                \"</td><td id='\" + cellId +\n                \"'></td></tr>\");\n            var labelCell = $('#' + labelCellId, table);\n            self.ctx.labelCells.push(labelCell);\n            var valueCell = $('#' + cellId, table);\n            self.ctx.valueCells.push(valueCell);\n        }\n    }    \n    \n    self.onResize();\n}\n\nself.onDataUpdated = function() {\n    for (var i = 0; i < self.ctx.valueCells.length; i++) {\n        var cellData = self.ctx.data[i];\n        if (cellData && cellData.data && cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            self.ctx.valueCells[i].html(value);\n        }\n    }    \n}\n\nself.onResize = function() {\n    var datasoirceTitleFontSize = self.ctx.height/8;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        datasoirceTitleFontSize = self.ctx.width/12;\n    }\n    datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n    for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n        self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n    }\n    var valueFontSize = self.ctx.height/9;\n    var labelFontSize = self.ctx.height/9;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        valueFontSize = self.ctx.width/15;\n        labelFontSize = self.ctx.width/15;\n    }\n    valueFontSize = Math.min(valueFontSize, 18);\n    labelFontSize = Math.min(labelFontSize, 18);\n\n    for (i = 0; i < self.ctx.valueCells; i++) {\n        self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n        self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n        self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n        self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n        self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n        self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n    }    \n}\n\nself.onDestroy = function() {\n}\n",
+        "controllerScript": "self.onInit = function() {\n    \n    self.ctx.datasourceTitleCells = [];\n    self.ctx.valueCells = [];\n    self.ctx.labelCells = [];\n    \n    for (var i=0; i < self.ctx.datasources.length; i++) {\n        var tbDatasource = self.ctx.datasources[i];\n\n        var datasourceId = 'tbDatasource' + i;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n\n        var datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n\n        datasourceContainer.append(\n            \"<div class='tbDatasource-title'>\" +\n            tbDatasource.name + \"</div>\"\n        );\n        \n        var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n        self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n        \n        var tableId = 'table' + i;\n        datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n\n        for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n            var dataKey = tbDatasource.dataKeys[a];\n            var labelCellId = 'labelCell' + a;\n            var cellId = 'cell' + a;\n            table.append(\"<tr><td id='\" + labelCellId + \"'>\" + dataKey.label +\n                \"</td><td id='\" + cellId +\n                \"'></td></tr>\");\n            var labelCell = $('#' + labelCellId, table);\n            self.ctx.labelCells.push(labelCell);\n            var valueCell = $('#' + cellId, table);\n            self.ctx.valueCells.push(valueCell);\n        }\n    }    \n    \n    self.onResize();\n}\n\nself.onDataUpdated = function() {\n    for (var i = 0; i < self.ctx.valueCells.length; i++) {\n        var cellData = self.ctx.data[i];\n        console.log(self.ctx); //del\n        if (cellData && cellData.data && cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            var textValue;\n            //toDo -> + IsNumber\n            \n            if (isNumber(value)) {\n                var decimals = self.ctx.decimals;\n                var units = self.ctx.units;\n                if (cellData.dataKey.decimals || cellData.dataKey.decimals === 0) {\n                    decimals = cellData.dataKey.decimals;\n                }\n                if (cellData.dataKey.units) {\n                    units = cellData.dataKey.units;\n                }\n                txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n            } else {\n                txtValue = value;\n            }\n            self.ctx.valueCells[i].html(txtValue);\n        }\n    }\n    \n    function isNumber(n) {\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n}\n\nself.onResize = function() {\n    var datasoirceTitleFontSize = self.ctx.height/8;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        datasoirceTitleFontSize = self.ctx.width/12;\n    }\n    datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n    for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n        self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n    }\n    var valueFontSize = self.ctx.height/9;\n    var labelFontSize = self.ctx.height/9;\n    if (self.ctx.width/self.ctx.height <= 1.5) {\n        valueFontSize = self.ctx.width/15;\n        labelFontSize = self.ctx.width/15;\n    }\n    valueFontSize = Math.min(valueFontSize, 18);\n    labelFontSize = Math.min(labelFontSize, 18);\n\n    for (i = 0; i < self.ctx.valueCells; i++) {\n        self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n        self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n        self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n        self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n        self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n        self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n    }    \n}\n\nself.onDestroy = function() {\n}\n",
         "settingsSchema": "{}",
         "dataKeySettingsSchema": "{}\n",
         "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Attributes card\"}"
@@ -95,7 +95,7 @@
         "resources": [],
         "templateHtml": "",
         "templateCss": "#container {\n    overflow: auto;\n}\n\n.tbDatasource-container {\n    width: 100%;\n    height: 100%;\n    overflow: hidden;\n}\n\n.tbDatasource-table {\n    width: 100%;\n    height: 100%;\n    border-collapse: collapse;\n    white-space: nowrap;\n    font-weight: 100;\n    text-align: right;\n}\n\n.tbDatasource-table td {\n    padding: 12px;\n    position: relative;\n    box-sizing: border-box;\n}\n\n.tbDatasource-data-key {\n    opacity: 0.7;\n    font-weight: 400;\n    font-size: 3.500rem;\n}\n\n.tbDatasource-value {\n    font-size: 5.000rem;\n}",
-        "controllerScript": "self.onInit = function() {\n\n    self.ctx.labelPosition = self.ctx.settings.labelPosition || 'left';\n    \n    if (self.ctx.datasources.length > 0) {\n        var tbDatasource = self.ctx.datasources[0];\n        var datasourceId = 'tbDatasource' + 0;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n        \n        self.ctx.datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n        \n        var tableId = 'table' + 0;\n        self.ctx.datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n        if (self.ctx.labelPosition === 'top') {\n            table.css('text-align', 'left');\n        }\n        \n        if (tbDatasource.dataKeys.length > 0) {\n            var dataKey = tbDatasource.dataKeys[0];\n            var labelCellId = 'labelCell' + 0;\n            var cellId = 'cell' + 0;\n            if (self.ctx.labelPosition === 'left') {\n                table.append(\n                    \"<tr><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            } else {\n                table.append(\n                    \"<tr style='vertical-align: bottom;'><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td></tr><tr><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            }\n            self.ctx.labelCell = $('#' + labelCellId, table);\n            self.ctx.valueCell = $('#' + cellId, table);\n            self.ctx.valueCell.html(0 + ' ' + self.ctx.units);\n        }\n    }\n    \n    $.fn.textWidth = function(){\n        var html_org = $(this).html();\n        var html_calc = '<span>' + html_org + '</span>';\n        $(this).html(html_calc);\n        var width = $(this).find('span:first').width();\n        $(this).html(html_org);\n        return width;\n    };    \n    \n    self.onResize();\n};\n\nself.onDataUpdated = function() {\n    \n    function isNumber(n) {\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n\n    if (self.ctx.valueCell && self.ctx.data.length > 0) {\n        var cellData = self.ctx.data[0];\n        if (cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            var txtValue;\n            if (isNumber(value)) {\n                txtValue = self.ctx.utils.formatValue(value, self.ctx.decimals, self.ctx.units);\n            } else {\n                txtValue = value;\n            }\n            self.ctx.valueCell.html(txtValue);\n            var targetWidth;\n            var minDelta;\n            if (self.ctx.labelPosition === 'left') {\n                targetWidth = self.ctx.datasourceContainer.width() - self.ctx.labelCell.width();\n                minDelta = self.ctx.width/16 + self.ctx.padding;\n            } else {\n                targetWidth = self.ctx.datasourceContainer.width();\n                minDelta = self.ctx.padding;\n            }\n            var delta = targetWidth - self.ctx.valueCell.textWidth();\n            var fontSize = self.ctx.valueFontSize;\n            if (targetWidth > minDelta) {\n                while (delta < minDelta && fontSize > 6) {\n                    fontSize--;\n                    self.ctx.valueCell.css('font-size', fontSize+'px');\n                    delta = targetWidth - self.ctx.valueCell.textWidth();\n                }\n            }\n        }\n    }    \n    \n};\n\nself.onResize = function() {\n    var labelFontSize;\n    if (self.ctx.labelPosition === 'top') {\n        self.ctx.padding = self.ctx.height/20;\n        labelFontSize = self.ctx.height/4;\n        self.ctx.valueFontSize = self.ctx.height/2;\n    } else {\n        self.ctx.padding = self.ctx.width/50;\n        labelFontSize = self.ctx.height/2.5;\n        self.ctx.valueFontSize = self.ctx.height/2;\n        if (self.ctx.width/self.ctx.height <= 2.7) {\n            labelFontSize = self.ctx.width/7;\n            self.ctx.valueFontSize = self.ctx.width/6;\n        }\n    }\n    self.ctx.padding = Math.min(12, self.ctx.padding);\n    \n    if (self.ctx.labelCell) {\n        self.ctx.labelCell.css('font-size', labelFontSize+'px');\n        self.ctx.labelCell.css('padding', self.ctx.padding+'px');\n    }\n    if (self.ctx.valueCell) {\n        self.ctx.valueCell.css('font-size', self.ctx.valueFontSize+'px');\n        self.ctx.valueCell.css('padding', self.ctx.padding+'px');\n    }    \n};\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        maxDataKeys: 1\n    };\n};\n\n\nself.onDestroy = function() {\n};\n",
+        "controllerScript": "self.onInit = function() {\n\n    self.ctx.labelPosition = self.ctx.settings.labelPosition || 'left';\n    \n    if (self.ctx.datasources.length > 0) {\n        var tbDatasource = self.ctx.datasources[0];\n        var datasourceId = 'tbDatasource' + 0;\n        self.ctx.$container.append(\n            \"<div id='\" + datasourceId +\n            \"' class='tbDatasource-container'></div>\"\n        );\n        \n        self.ctx.datasourceContainer = $('#' + datasourceId,\n            self.ctx.$container);\n        \n        var tableId = 'table' + 0;\n        self.ctx.datasourceContainer.append(\n            \"<table id='\" + tableId +\n            \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n        );\n        var table = $('#' + tableId, self.ctx.$container);\n        if (self.ctx.labelPosition === 'top') {\n            table.css('text-align', 'left');\n        }\n        \n        if (tbDatasource.dataKeys.length > 0) {\n            var dataKey = tbDatasource.dataKeys[0];\n            var labelCellId = 'labelCell' + 0;\n            var cellId = 'cell' + 0;\n            if (self.ctx.labelPosition === 'left') {\n                table.append(\n                    \"<tr><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            } else {\n                table.append(\n                    \"<tr style='vertical-align: bottom;'><td class='tbDatasource-data-key' id='\" + labelCellId +\"'>\" +\n                    dataKey.label +\n                    \"</td></tr><tr><td class='tbDatasource-value' id='\" +\n                    cellId +\n                    \"'></td></tr>\");\n            }\n            self.ctx.labelCell = $('#' + labelCellId, table);\n            self.ctx.valueCell = $('#' + cellId, table);\n            self.ctx.valueCell.html(0 + ' ' + self.ctx.units);\n        }\n    }\n    \n    $.fn.textWidth = function(){\n        var html_org = $(this).html();\n        var html_calc = '<span>' + html_org + '</span>';\n        $(this).html(html_calc);\n        var width = $(this).find('span:first').width();\n        $(this).html(html_org);\n        return width;\n    };    \n    \n    self.onResize();\n};\n\nself.onDataUpdated = function() {\n    \n    function isNumber(n) {\n        return !isNaN(parseFloat(n)) && isFinite(n);\n    }\n\n    if (self.ctx.valueCell && self.ctx.data.length > 0) {\n        var cellData = self.ctx.data[0];\n        if (cellData.data.length > 0) {\n            var tvPair = cellData.data[cellData.data.length -\n                1];\n            var value = tvPair[1];\n            var txtValue;\n            if (isNumber(value)) {\n                var decimals = self.ctx.decimals;\n                var units = self.ctx.units;\n                if (self.ctx.datasources.length > 0 && self.ctx.datasources[0].dataKeys.length > 0) {\n                    dataKey = self.ctx.datasources[0].dataKeys[0];\n                    if (dataKey.decimals || dataKey.decimals === 0) {\n                        decimals = dataKey.decimals;\n                    }\n                    if (dataKey.units) {\n                        units = dataKey.units;\n                    }\n                }\n                txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n            } else {\n                txtValue = value;\n            }\n            self.ctx.valueCell.html(txtValue);\n            var targetWidth;\n            var minDelta;\n            if (self.ctx.labelPosition === 'left') {\n                targetWidth = self.ctx.datasourceContainer.width() - self.ctx.labelCell.width();\n                minDelta = self.ctx.width/16 + self.ctx.padding;\n            } else {\n                targetWidth = self.ctx.datasourceContainer.width();\n                minDelta = self.ctx.padding;\n            }\n            var delta = targetWidth - self.ctx.valueCell.textWidth();\n            var fontSize = self.ctx.valueFontSize;\n            if (targetWidth > minDelta) {\n                while (delta < minDelta && fontSize > 6) {\n                    fontSize--;\n                    self.ctx.valueCell.css('font-size', fontSize+'px');\n                    delta = targetWidth - self.ctx.valueCell.textWidth();\n                }\n            }\n        }\n    }    \n    \n};\n\nself.onResize = function() {\n    var labelFontSize;\n    if (self.ctx.labelPosition === 'top') {\n        self.ctx.padding = self.ctx.height/20;\n        labelFontSize = self.ctx.height/4;\n        self.ctx.valueFontSize = self.ctx.height/2;\n    } else {\n        self.ctx.padding = self.ctx.width/50;\n        labelFontSize = self.ctx.height/2.5;\n        self.ctx.valueFontSize = self.ctx.height/2;\n        if (self.ctx.width/self.ctx.height <= 2.7) {\n            labelFontSize = self.ctx.width/7;\n            self.ctx.valueFontSize = self.ctx.width/6;\n        }\n    }\n    self.ctx.padding = Math.min(12, self.ctx.padding);\n    \n    if (self.ctx.labelCell) {\n        self.ctx.labelCell.css('font-size', labelFontSize+'px');\n        self.ctx.labelCell.css('padding', self.ctx.padding+'px');\n    }\n    if (self.ctx.valueCell) {\n        self.ctx.valueCell.css('font-size', self.ctx.valueFontSize+'px');\n        self.ctx.valueCell.css('padding', self.ctx.padding+'px');\n    }    \n};\n\nself.typeParameters = function() {\n    return {\n        maxDatasources: 1,\n        maxDataKeys: 1\n    };\n};\n\n\nself.onDestroy = function() {\n};\n",
         "settingsSchema": "{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"labelPosition\": {\n                \"title\": \"Label position\",\n                \"type\": \"string\",\n                \"default\": \"left\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        {\n           \"key\": \"labelPosition\",\n           \"type\": \"rc-select\",\n           \"multiple\": false,\n           \"items\": [\n               {\n                   \"value\": \"left\",\n                   \"label\": \"Left\"\n               },\n               {\n                   \"value\": \"top\",\n                   \"label\": \"Top\"\n               }\n            ]\n        }\n    ]\n}",
         "dataKeySettingsSchema": "{}\n",
         "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temp\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.2392660816082064,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#ff5722\",\"color\":\"rgba(255, 255, 255, 0.87)\",\"padding\":\"16px\",\"settings\":{\"labelPosition\":\"top\"},\"title\":\"Simple card\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"units\":\"°C\",\"decimals\":0,\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}"
diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json
index a264ba6..71e9fa7 100644
--- a/application/src/main/data/json/system/widget_bundles/charts.json
+++ b/application/src/main/data/json/system/widget_bundles/charts.json
@@ -35,7 +35,7 @@
         "resources": [],
         "templateHtml": "",
         "templateCss": ".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n",
-        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx);    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
+        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx);    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
         "settingsSchema": "{}",
         "dataKeySettingsSchema": "{}",
         "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
@@ -147,10 +147,10 @@
         "resources": [],
         "templateHtml": "",
         "templateCss": ".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n",
-        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, 'bar');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
+        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, 'bar');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
         "settingsSchema": "{}",
         "dataKeySettingsSchema": "{}",
-        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":true,\"tooltipIndividual\":false},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
+        "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
       }
     },
     {
@@ -163,7 +163,7 @@
         "resources": [],
         "templateHtml": "",
         "templateCss": ".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n",
-        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, 'state');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n    return {\n        stateData: true\n    };\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
+        "controllerScript": "self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, 'state');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n    return {\n        stateData: true\n    };\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n",
         "settingsSchema": "{}",
         "dataKeySettingsSchema": "{}",
         "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n    return 'On';\\n} else if (value === 0) {\\n    return 'Off';\\n} else {\\n    return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n    return 'On';\\n} else if (value === 0) {\\n    return 'Off';\\n} else {\\n    return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
diff --git a/application/src/main/data/upgrade/2.1.1/schema_update.cql b/application/src/main/data/upgrade/2.1.1/schema_update.cql
index c477e8a..36ac8e4 100644
--- a/application/src/main/data/upgrade/2.1.1/schema_update.cql
+++ b/application/src/main/data/upgrade/2.1.1/schema_update.cql
@@ -20,7 +20,7 @@ DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
 DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
 
 DROP TABLE IF EXISTS thingsboard.entity_views;
-ControllerSqlTestSuite
+
 CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
     id timeuuid,
     entity_id timeuuid,
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 720bb9f..f35df67 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -391,6 +391,7 @@ audit_log:
       "user": "${AUDIT_LOG_MASK_USER:W}"
       "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
       "alarm": "${AUDIT_LOG_MASK_ALARM:W}"
+      "entity_view": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
   sink:
     # Type of external sink. possible options: none, elasticsearch
     type: "${AUDIT_LOG_SINK_TYPE:none}"
diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java
index f0fb51e..2e7b412 100644
--- a/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java
+++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java
@@ -73,4 +73,6 @@ public abstract class DeviceAwareSessionContext implements SessionContext {
     public Device getDevice() {
         return device;
     }
+
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index f35b890..ebd0560 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -54,6 +54,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
@@ -141,7 +142,7 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
             if (entityViews != null && !entityViews.isEmpty()) {
                 throw new DataValidationException("Can't delete asset that is assigned to entity views!");
             }
-        } catch (Exception e) {
+        } catch (ExecutionException | InterruptedException e) {
             log.error("Exception while finding entity views for assetId [{}]", assetId, e);
             throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
         }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
index 6f9ea62..2be5ba4 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -58,6 +58,7 @@ import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE;
@@ -158,7 +159,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
             if (entityViews != null && !entityViews.isEmpty()) {
                 throw new DataValidationException("Can't delete device that is assigned to entity views!");
             }
-        } catch (Exception e) {
+        } catch (ExecutionException | InterruptedException e) {
             log.error("Exception while finding entity views for deviceId [{}]", deviceId, e);
             throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e);
         }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
index f0b29cb..c8baaf7 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
@@ -170,7 +170,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
 
     private MqttPublishMessage createMqttPublishMsg(DeviceSessionCtx ctx, String topic, JsonElement json) {
         MqttFixedHeader mqttFixedHeader =
-                new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_LEAST_ONCE, false, 0);
+                new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0);
         MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId());
         ByteBuf payload = ALLOCATOR.buffer();
         payload.writeBytes(GSON.toJson(json).getBytes(UTF8));
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index 185b7a8..ed0c8c6 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -51,6 +51,9 @@ import javax.security.cert.X509Certificate;
 import java.net.InetSocketAddress;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*;
 import static io.netty.handler.codec.mqtt.MqttMessageType.*;
@@ -75,6 +78,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
     private final RelationService relationService;
     private final QuotaService quotaService;
     private final SslHandler sslHandler;
+    private final ConcurrentMap<String, Integer> mqttQoSMap;
+
     private volatile boolean connected;
     private volatile InetSocketAddress address;
     private volatile GatewaySessionCtx gatewaySessionCtx;
@@ -86,7 +91,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
         this.relationService = relationService;
         this.authService = authService;
         this.adaptor = adaptor;
-        this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor);
+        this.mqttQoSMap = new ConcurrentHashMap<>();
+        this.deviceSessionCtx = new DeviceSessionCtx(processor, authService, adaptor, mqttQoSMap);
         this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
         this.sslHandler = sslHandler;
         this.quotaService = quotaService;
@@ -166,18 +172,25 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
 
     private void handleMqttPublishMsg(String topicName, int msgId, MqttPublishMessage mqttMsg) {
         try {
-            if (topicName.equals(GATEWAY_TELEMETRY_TOPIC)) {
-                gatewaySessionCtx.onDeviceTelemetry(mqttMsg);
-            } else if (topicName.equals(GATEWAY_ATTRIBUTES_TOPIC)) {
-                gatewaySessionCtx.onDeviceAttributes(mqttMsg);
-            } else if (topicName.equals(GATEWAY_ATTRIBUTES_REQUEST_TOPIC)) {
-                gatewaySessionCtx.onDeviceAttributesRequest(mqttMsg);
-            } else if (topicName.equals(GATEWAY_RPC_TOPIC)) {
-                gatewaySessionCtx.onDeviceRpcResponse(mqttMsg);
-            } else if (topicName.equals(GATEWAY_CONNECT_TOPIC)) {
-                gatewaySessionCtx.onDeviceConnect(mqttMsg);
-            } else if (topicName.equals(GATEWAY_DISCONNECT_TOPIC)) {
-                gatewaySessionCtx.onDeviceDisconnect(mqttMsg);
+            switch (topicName) {
+                case GATEWAY_TELEMETRY_TOPIC:
+                    gatewaySessionCtx.onDeviceTelemetry(mqttMsg);
+                    break;
+                case GATEWAY_ATTRIBUTES_TOPIC:
+                    gatewaySessionCtx.onDeviceAttributes(mqttMsg);
+                    break;
+                case GATEWAY_ATTRIBUTES_REQUEST_TOPIC:
+                    gatewaySessionCtx.onDeviceAttributesRequest(mqttMsg);
+                    break;
+                case GATEWAY_RPC_TOPIC:
+                    gatewaySessionCtx.onDeviceRpcResponse(mqttMsg);
+                    break;
+                case GATEWAY_CONNECT_TOPIC:
+                    gatewaySessionCtx.onDeviceConnect(mqttMsg);
+                    break;
+                case GATEWAY_DISCONNECT_TOPIC:
+                    gatewaySessionCtx.onDeviceDisconnect(mqttMsg);
+                    break;
             }
         } catch (RuntimeException | AdaptorException e) {
             log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
@@ -225,52 +238,71 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
         log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
         List<Integer> grantedQoSList = new ArrayList<>();
         for (MqttTopicSubscription subscription : mqttMsg.payload().topicSubscriptions()) {
-            String topicName = subscription.topicName();
-            //TODO: handle this qos level.
+            String topic = subscription.topicName();
             MqttQoS reqQoS = subscription.qualityOfService();
             try {
-                if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) {
-                    AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg);
-                    processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
-                    grantedQoSList.add(getMinSupportedQos(reqQoS));
-                } else if (topicName.equals(DEVICE_RPC_REQUESTS_SUB_TOPIC)) {
-                    AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg);
-                    processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
-                    grantedQoSList.add(getMinSupportedQos(reqQoS));
-                } else if (topicName.equals(DEVICE_RPC_RESPONSE_SUB_TOPIC)) {
-                    grantedQoSList.add(getMinSupportedQos(reqQoS));
-                } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) {
-                    deviceSessionCtx.setAllowAttributeResponses();
-                    grantedQoSList.add(getMinSupportedQos(reqQoS));
-                } else if (topicName.equals(GATEWAY_ATTRIBUTES_TOPIC)) {
-                    grantedQoSList.add(getMinSupportedQos(reqQoS));
-                } else {
-                    log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS);
-                    grantedQoSList.add(FAILURE.value());
+                switch (topic) {
+                    case DEVICE_ATTRIBUTES_TOPIC: {
+                        AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg);
+                        processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
+                        registerSubQoS(topic, grantedQoSList, reqQoS);
+                        break;
+                    }
+                    case DEVICE_RPC_REQUESTS_SUB_TOPIC: {
+                        AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, SUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg);
+                        processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
+                        registerSubQoS(topic, grantedQoSList, reqQoS);
+                        break;
+                    }
+                    case DEVICE_RPC_RESPONSE_SUB_TOPIC:
+                    case GATEWAY_ATTRIBUTES_TOPIC:
+                    case GATEWAY_RPC_TOPIC:
+                        registerSubQoS(topic, grantedQoSList, reqQoS);
+                        break;
+                    case DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
+                        deviceSessionCtx.setAllowAttributeResponses();
+                        registerSubQoS(topic, grantedQoSList, reqQoS);
+                        break;
+                    default:
+                        log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
+                        grantedQoSList.add(FAILURE.value());
+                        break;
                 }
             } catch (AdaptorException e) {
-                log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topicName, reqQoS);
+                log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS);
                 grantedQoSList.add(FAILURE.value());
             }
         }
         ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
     }
 
+    private void registerSubQoS(String topic, List<Integer> grantedQoSList, MqttQoS reqQoS) {
+        grantedQoSList.add(getMinSupportedQos(reqQoS));
+        mqttQoSMap.put(topic, getMinSupportedQos(reqQoS));
+    }
+
     private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
         if (!checkConnected(ctx)) {
             return;
         }
         log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId());
         for (String topicName : mqttMsg.payload().topics()) {
+            mqttQoSMap.remove(topicName);
             try {
-                if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) {
-                    AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg);
-                    processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
-                } else if (topicName.equals(DEVICE_RPC_REQUESTS_SUB_TOPIC)) {
-                    AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg);
-                    processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
-                } else if (topicName.equals(DEVICE_ATTRIBUTES_RESPONSES_TOPIC)) {
-                    deviceSessionCtx.setDisallowAttributeResponses();
+                switch (topicName) {
+                    case DEVICE_ATTRIBUTES_TOPIC: {
+                        AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_ATTRIBUTES_REQUEST, mqttMsg);
+                        processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
+                        break;
+                    }
+                    case DEVICE_RPC_REQUESTS_SUB_TOPIC: {
+                        AdaptorToSessionActorMsg msg = adaptor.convertToActorMsg(deviceSessionCtx, UNSUBSCRIBE_RPC_COMMANDS_REQUEST, mqttMsg);
+                        processor.process(new BasicTransportToDeviceSessionActorMsg(deviceSessionCtx.getDevice(), msg));
+                        break;
+                    }
+                    case DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
+                        deviceSessionCtx.setDisallowAttributeResponses();
+                        break;
                 }
             } catch (AdaptorException e) {
                 log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
index 9367a04..3dbb3ef 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
@@ -30,13 +30,15 @@ import org.thingsboard.server.common.transport.auth.DeviceAuthService;
 import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
 import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
 
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @author Andrew Shvayka
  */
 @Slf4j
-public class DeviceSessionCtx extends DeviceAwareSessionContext {
+public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
 
     private final MqttTransportAdaptor adaptor;
     private final MqttSessionId sessionId;
@@ -44,8 +46,8 @@ public class DeviceSessionCtx extends DeviceAwareSessionContext {
     private volatile boolean allowAttributeResponses;
     private AtomicInteger msgIdSeq = new AtomicInteger(0);
 
-    public DeviceSessionCtx(SessionMsgProcessor processor, DeviceAuthService authService, MqttTransportAdaptor adaptor) {
-        super(processor, authService);
+    public DeviceSessionCtx(SessionMsgProcessor processor, DeviceAuthService authService, MqttTransportAdaptor adaptor, ConcurrentMap<String, Integer> mqttQoSMap) {
+        super(processor, authService, mqttQoSMap);
         this.adaptor = adaptor;
         this.sessionId = new MqttSessionId();
     }
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
index dd9c921..e5be5c7 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java
@@ -38,13 +38,15 @@ import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
 
 import java.nio.charset.Charset;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
+import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Created by ashvayka on 19.01.17.
  */
-public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext {
+public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext {
 
     private static final Gson GSON = new Gson();
     private static final Charset UTF8 = Charset.forName("UTF-8");
@@ -56,8 +58,8 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext {
     private volatile boolean closed;
     private AtomicInteger msgIdSeq = new AtomicInteger(0);
 
-    public GatewayDeviceSessionCtx(GatewaySessionCtx parent, Device device) {
-        super(parent.getProcessor(), parent.getAuthService(), device);
+    public GatewayDeviceSessionCtx(GatewaySessionCtx parent, Device device, ConcurrentMap<String, Integer> mqttQoSMap) {
+        super(parent.getProcessor(), parent.getAuthService(), device, mqttQoSMap);
         this.parent = parent;
         this.sessionId = new MqttSessionId();
     }
@@ -195,7 +197,7 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext {
 
     private MqttPublishMessage createMqttPublishMsg(String topic, JsonElement json) {
         MqttFixedHeader mqttFixedHeader =
-                new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.AT_LEAST_ONCE, false, 0);
+                new MqttFixedHeader(MqttMessageType.PUBLISH, false, getQoSForTopic(topic), false, 0);
         MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, msgIdSeq.incrementAndGet());
         ByteBuf payload = ALLOCATOR.buffer();
         payload.writeBytes(GSON.toJson(json).getBytes(UTF8));
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
index 69ed17f..98ad6d2 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
@@ -43,6 +43,7 @@ import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
 import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentMap;
 import java.util.stream.Collectors;
 
 import static org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor.validateJsonPayload;
@@ -63,6 +64,7 @@ public class GatewaySessionCtx {
     private final DeviceAuthService authService;
     private final RelationService relationService;
     private final Map<String, GatewayDeviceSessionCtx> devices;
+    private final ConcurrentMap<String, Integer> mqttQoSMap;
     private ChannelHandlerContext channel;
 
     public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, DeviceSessionCtx gatewaySessionCtx) {
@@ -73,6 +75,7 @@ public class GatewaySessionCtx {
         this.gateway = gatewaySessionCtx.getDevice();
         this.gatewaySessionId = gatewaySessionCtx.getSessionId();
         this.devices = new HashMap<>();
+        this.mqttQoSMap = gatewaySessionCtx.getMqttQoSMap();
     }
 
     public void onDeviceConnect(MqttPublishMessage msg) throws AdaptorException {
@@ -96,7 +99,7 @@ public class GatewaySessionCtx {
                 relationService.saveRelationAsync(new EntityRelation(gateway.getId(), device.getId(), "Created"));
                 processor.onDeviceAdded(device);
             }
-            GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device);
+            GatewayDeviceSessionCtx ctx = new GatewayDeviceSessionCtx(this, device, mqttQoSMap);
             devices.put(deviceName, ctx);
             log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
             processor.process(new BasicTransportToDeviceSessionActorMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg())));
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java
new file mode 100644
index 0000000..f085064
--- /dev/null
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/MqttDeviceAwareSessionContext.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright © 2016-2018 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.transport.mqtt.session;
+
+import io.netty.handler.codec.mqtt.MqttQoS;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.transport.SessionMsgProcessor;
+import org.thingsboard.server.common.transport.auth.DeviceAuthService;
+import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Created by ashvayka on 30.08.18.
+ */
+public abstract class MqttDeviceAwareSessionContext extends DeviceAwareSessionContext {
+
+    private final ConcurrentMap<String, Integer> mqttQoSMap;
+
+    public MqttDeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService, ConcurrentMap<String, Integer> mqttQoSMap) {
+        super(processor, authService);
+        this.mqttQoSMap = mqttQoSMap;
+    }
+
+    public MqttDeviceAwareSessionContext(SessionMsgProcessor processor, DeviceAuthService authService, Device device, ConcurrentMap<String, Integer> mqttQoSMap) {
+        super(processor, authService, device);
+        this.mqttQoSMap = mqttQoSMap;
+    }
+
+    public ConcurrentMap<String, Integer> getMqttQoSMap() {
+        return mqttQoSMap;
+    }
+
+    public MqttQoS getQoSForTopic(String topic) {
+        Integer qos = mqttQoSMap.get(topic);
+        if (qos != null) {
+            return MqttQoS.valueOf(qos);
+        } else {
+            return MqttQoS.AT_LEAST_ONCE;
+        }
+    }
+
+}

ui/.stylelintrc 6(+3 -3)

diff --git a/ui/.stylelintrc b/ui/.stylelintrc
index c145c8b..e878729 100644
--- a/ui/.stylelintrc
+++ b/ui/.stylelintrc
@@ -251,7 +251,7 @@
       "fill",
       "stroke"
     ],
-    "property-no-vendor-prefix": null,
+    "property-no-vendor-prefix": true,
     "rule-empty-line-before": ["always", {
       "except": ["first-nested"],
       "ignore": ["after-comment"]
@@ -272,7 +272,7 @@
     "selector-max-type": 5,
     "selector-max-universal": 1,
     "selector-no-qualifying-type": null,
-    "selector-no-vendor-prefix": null,
+    "selector-no-vendor-prefix": true,
     "selector-type-no-unknown": [true, {
       "ignoreTypes": [
         "/^md-/",
@@ -287,6 +287,6 @@
     "value-list-comma-newline-after": "always-multi-line",
     "value-list-comma-newline-before": "never-multi-line",
     "value-list-comma-space-after": "always-single-line",
-    "value-no-vendor-prefix": null
+    "value-no-vendor-prefix": true
   }
 }

ui/package.json 16(+15 -1)

diff --git a/ui/package.json b/ui/package.json
index 417948e..7874170 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -119,7 +119,7 @@
     "ng-annotate-loader": "^0.1.1",
     "ngtemplate-loader": "^1.3.1",
     "node-sass": "^4.5.3",
-    "postcss-loader": "^0.13.0",
+    "postcss-loader": "^3.0.0",
     "raw-loader": "^0.5.1",
     "react-hot-loader": "^3.0.0-beta.6",
     "sass-loader": "^4.0.2",
@@ -145,5 +145,19 @@
       "node_modules",
       "target"
     ]
+  },
+  "browserslist": [
+    "> 0.5%",
+    "last 2 versions",
+    "Firefox ESR",
+    "not ie <= 10",
+    "not ie_mob <= 10",
+    "not bb <= 10",
+    "not op_mob <= 12.1"
+  ],
+  "postcss": {
+    "plugins": {
+      "autoprefixer": true
+    }
   }
 }
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
index e0a9afd..b4a696a 100644
--- a/ui/src/app/components/dashboard.scss
+++ b/ui/src/app/components/dashboard.scss
@@ -22,7 +22,7 @@ div.tb-widget {
   overflow: hidden;
   outline: none;
 
-  @include transition(all .2s ease-in-out);
+  transition: all .2s ease-in-out;
 
   .tb-widget-title {
     max-height: 60px;
@@ -99,7 +99,7 @@ md-content.tb-dashboard-content {
   outline: none;
 
   .gridster-item {
-    @include transition(none);
+    transition: none;
   }
 }
 
diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss
index 4313848..50e110b 100644
--- a/ui/src/app/components/grid.scss
+++ b/ui/src/app/components/grid.scss
@@ -20,7 +20,7 @@
 }
 
 .tb-card-item {
-  @include transition(all .2s ease-in-out);
+  transition: all .2s ease-in-out;
 
   md-card-content {
     max-height: 53px;
@@ -46,7 +46,7 @@
 .tb-current-item {
   opacity: .5;
 
-  @include transform(scale(1.05));
+  transform: scale(1.05);
 }
 
 #tb-vertical-container {
diff --git a/ui/src/app/components/menu-link.scss b/ui/src/app/components/menu-link.scss
index 299797c..be274d3 100644
--- a/ui/src/app/components/menu-link.scss
+++ b/ui/src/app/components/menu-link.scss
@@ -16,7 +16,7 @@
 @import "~compass-sass-mixins/lib/compass";
 
 .md-button-toggle .md-toggle-icon.tb-toggled {
-  @include transform(rotateZ(180deg));
+  transform: rotateZ(180deg);
 }
 
 .tb-menu-toggle-list.ng-hide {
@@ -28,7 +28,7 @@
   z-index: 1;
   overflow: hidden;
 
-  @include transition(.75s cubic-bezier(.35, 0, .25, 1));
+  transition: .75s cubic-bezier(.35, 0, .25, 1);
 
-  @include transition-property(height);
+  transition-property: height;
 }
diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss
index cca9d07..d324abe 100644
--- a/ui/src/app/components/react/json-form.scss
+++ b/ui/src/app/components/react/json-form.scss
@@ -28,7 +28,7 @@ $input-label-float-scale: .75 !default;
   line-height: 12px;
   color: rgb(244, 67, 54);
 
-  @include transition(all 450ms cubic-bezier(.23, 1, .32, 1) 0ms);
+  transition: all 450ms cubic-bezier(.23, 1, .32, 1) 0ms;
 }
 
 .tb-container {
@@ -77,13 +77,12 @@ label.tb-label {
   bottom: 100%;
   left: 0;
   color: rgba(0, 0, 0, .54);
-  transform-origin: left top;
-  -webkit-font-smoothing: antialiased;
 
-  @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
+  transition: transform $swift-ease-out-timing-function $swift-ease-out-duration, width $swift-ease-out-timing-function $swift-ease-out-duration;
 
-  @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
-  width $swift-ease-out-timing-function $swift-ease-out-duration);
+  transform: translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale);
+  transform-origin: left top;
+  -webkit-font-smoothing: antialiased;
 
   &.tb-focused {
     color: rgb(96, 125, 139);
diff --git a/ui/src/app/components/side-menu.scss b/ui/src/app/components/side-menu.scss
index 46da8db..f6efcb9 100644
--- a/ui/src/app/components/side-menu.scss
+++ b/ui/src/app/components/side-menu.scss
@@ -53,7 +53,7 @@
   margin: auto 0 auto auto;
   background-size: 100% auto;
 
-  @include transition(transform .3s, ease-in-out);
+  transition: transform .3s, ease-in-out;
 }
 
 .tb-side-menu .md-button {
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
index 88a37a8..8110322 100644
--- a/ui/src/app/components/widget/action/manage-widget-actions.directive.js
+++ b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
@@ -244,13 +244,18 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
             vm.widgetActions[actionSourceId] = targetActions;
         }
         if (prevActionId) {
-            var index = getActionIndex(prevActionId, vm.allActions);
-            if (index > -1) {
-                vm.allActions[index] = action;
+            const indexInTarget = getActionIndex(prevActionId, targetActions);
+            const indexInAllActions = getActionIndex(prevActionId, vm.allActions);
+            if (indexInTarget > -1) {
+                targetActions[indexInTarget] = widgetAction;
+            } else if (indexInAllActions > -1) {
+                const prevActionSourceId = vm.allActions[indexInAllActions].actionSourceId;
+                const index = getActionIndex(prevActionId,vm.widgetActions[prevActionSourceId]);
+                vm.widgetActions[prevActionSourceId].splice(index,1);
+                targetActions.push(widgetAction);
             }
-            index = getActionIndex(prevActionId, targetActions);
-            if (index > -1) {
-                targetActions[index] = widgetAction;
+            if (indexInAllActions > -1) {
+                vm.allActions[indexInAllActions] = action;
             }
         } else {
             vm.allActions.push(action);
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 00a0653..715c4da 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -75,13 +75,13 @@ section.tb-dashboard-toolbar {
 
   &.tb-dashboard-toolbar-opened {
     right: 0;
-    // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+    // transition: right .3s cubic-bezier(.55, 0, .55, .2);
   }
 
   &.tb-dashboard-toolbar-closed {
     right: 18px;
 
-    @include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
+    transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s;
   }
 }
 
@@ -102,14 +102,14 @@ section.tb-dashboard-toolbar {
         margin-top: $toolbar-height;
       }
 
-      @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+      transition: margin-top .3s cubic-bezier(.55, 0, .55, .2);
     }
   }
 
   &.tb-dashboard-toolbar-closed {
     margin-top: 0;
 
-    @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
+    transition: margin-top .3s cubic-bezier(.55, 0, .55, .2) .2s;
   }
 
   .tb-dashboard-layouts {
@@ -133,7 +133,7 @@ section.tb-powered-by-footer {
   position: absolute;
   right: 25px;
   bottom: 5px;
-  z-index: 3;
+  z-index: 30;
   pointer-events: none;
 
   span {
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 829f174..79653a4 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -146,7 +146,7 @@
                         ng-style="{minWidth: vm.rightLayoutWidth(),
                                    maxWidth: vm.rightLayoutWidth(),
                                    height: vm.rightLayoutHeight(),
-                                   zIndex: 12}"
+                                   zIndex: 25}"
                         md-component-id="right-dashboard-layout"
                         aria-label="Right dashboard layout"
                         md-is-open="vm.rightLayoutOpened"
diff --git a/ui/src/app/dashboard/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss
index 8e6626c..111d67a 100644
--- a/ui/src/app/dashboard/dashboard-card.scss
+++ b/ui/src/app/dashboard/dashboard-card.scss
@@ -16,12 +16,12 @@
 
 .tb-dashboard-assigned-customers {
   display: block;
-  display: -webkit-box;
+  display: -webkit-box; /* stylelint-disable-line value-no-vendor-prefix */
   height: 34px;
   margin-bottom: 4px;
   overflow: hidden;
   text-overflow: ellipsis;
   -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
+  -webkit-box-orient: vertical; /* stylelint-disable-line property-no-vendor-prefix */
 }
 
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
index 80fd876..c247634 100644
--- a/ui/src/app/dashboard/dashboard-toolbar.scss
+++ b/ui/src/app/dashboard/dashboard-toolbar.scss
@@ -31,7 +31,7 @@ tb-dashboard-toolbar {
           &.md-fab {
             opacity: 1;
 
-            @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
+            transition: opacity .3s cubic-bezier(.55, 0, .55, .2);
 
             .md-fab-toolbar-background {
               background-color: $primary-default !important;
@@ -50,7 +50,7 @@ tb-dashboard-toolbar {
           line-height: 36px;
           opacity: .5;
 
-          @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
+          transition: opacity .3s cubic-bezier(.55, 0, .55, .2) .2s;
 
           md-icon {
             position: absolute;
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index 48c1553..43b8329 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -42,7 +42,7 @@
     border: none;
     opacity: .75;
 
-    @include transition(opacity .35s);
+    transition: opacity .35s;
   }
 
   a:hover,
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 8f77300..d6d264d 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -84,9 +84,6 @@
 
       .tb-panel-title {
         min-width: 150px;
-        -webkit-user-select: none;
-        -moz-user-select: none;
-        -ms-user-select: none;
         user-select: none;
       }
 
@@ -163,10 +160,6 @@
 .fc-canvas {
   min-width: 100%;
   min-height: 100%;
-  -webkit-user-select: none;
-  -khtml-user-select: none;
-  -moz-user-select: none;
-  -ms-user-select: none;
   user-select: none;
   outline: none;
   -webkit-touch-callout: none;
@@ -441,13 +434,7 @@
 }
 
 .fc-noselect {
-  -webkit-touch-callout: none; /* iOS Safari */
-  -webkit-user-select: none; /* Safari */
-  -khtml-user-select: none; /* Konqueror HTML */
-  -moz-user-select: none; /* Firefox */
-  -ms-user-select: none; /* Internet Explorer/Edge */
-  user-select: none; /* Non-prefixed version, currently
-                                  supported by Chrome and Opera */
+  user-select: none;
 }
 
 .fc-edge-label {
@@ -495,7 +482,6 @@
   font-weight: 600;
   text-align: center;
   white-space: nowrap;
-  -webkit-transform: translate(-50%, -50%);
   transform: translate(-50%, -50%);
 
   span {
diff --git a/ui/src/app/rulechain/script/node-script-test.scss b/ui/src/app/rulechain/script/node-script-test.scss
index 1dd174d..e68691d 100644
--- a/ui/src/app/rulechain/script/node-script-test.scss
+++ b/ui/src/app/rulechain/script/node-script-test.scss
@@ -29,7 +29,7 @@ md-dialog.tb-node-script-test-dialog {
   }
 
   .tb-split {
-    @include box-sizing(border-box);
+    box-sizing: border-box;
     overflow-x: hidden;
     overflow-y: auto;
   }
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index 620f313..6cfe7a0 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -371,7 +371,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP
                     content = strContent;
                 }
             } else {
-                content = defaultContent(key, value);
+                var decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : vm.widgetConfig.decimals;
+                var units = contentInfo.units || vm.widgetConfig.units;
+                content = vm.ctx.utils.formatValue(value, decimals, units, true);
             }
             return content;
         } else {
@@ -379,14 +381,6 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP
         }
     }
 
-    function defaultContent(key, value) {
-        if (angular.isDefined(value)) {
-            return value;
-        } else {
-            return '';
-        }
-    }
-
     function defaultStyle(/*key, value*/) {
         return {};
     }
@@ -542,7 +536,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdP
 
             vm.contentsInfo[dataKey.label] = {
                 useCellContentFunction: useCellContentFunction,
-                cellContentFunction: cellContentFunction
+                cellContentFunction: cellContentFunction,
+                units: dataKey.units,
+                decimals: dataKey.decimals
             };
 
             var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index 1f363d6..acaaf10 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -333,6 +333,7 @@ export default class TbFlot {
                         lineWidth: 0,
                         fill: 0.9
                 }
+                ctx.defaultBarWidth = settings.defaultBarWidth || 600;
             }
 
             if (this.chartType === 'state') {
@@ -476,7 +477,11 @@ export default class TbFlot {
         this.options.yaxes = angular.copy(this.yaxes);
         if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
             if (this.chartType === 'bar') {
-                this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') {
+                    this.options.series.bars.barWidth = this.ctx.defaultBarWidth;
+                } else {
+                    this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                }
             }
             this.options.xaxis.min = this.subscription.timeWindow.minTime;
             this.options.xaxis.max = this.subscription.timeWindow.maxTime;
@@ -594,7 +599,11 @@ export default class TbFlot {
                     this.options.xaxis.min = this.subscription.timeWindow.minTime;
                     this.options.xaxis.max = this.subscription.timeWindow.maxTime;
                     if (this.chartType === 'bar') {
-                        this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                        if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') {
+                            this.options.series.bars.barWidth = this.ctx.defaultBarWidth;
+                        } else {
+                            this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                        }
                     }
 
                     if (axisVisibilityChanged) {
@@ -603,7 +612,11 @@ export default class TbFlot {
                         this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
                         this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
                         if (this.chartType === 'bar') {
-                            this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                            if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') {
+                                this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth;
+                            } else {
+                                this.ctx.plot.getOptions().series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
+                            }
                         }
                         this.updateData();
                     }
@@ -810,238 +823,257 @@ export default class TbFlot {
         }
     }
 
-    static get settingsSchema() {
-        return {
+    static settingsSchema(chartType) {
+
+        var schema = {
             "schema": {
                 "type": "object",
                 "title": "Settings",
                 "properties": {
-                    "stack": {
-                        "title": "Stacking",
-                        "type": "boolean",
-                        "default": false
-                    },
-                    "smoothLines": {
-                        "title": "Display smooth (curved) lines",
-                        "type": "boolean",
-                        "default": false
-                    },
-                    "shadowSize": {
-                        "title": "Shadow size",
-                        "type": "number",
-                        "default": 4
-                    },
-                    "fontColor": {
-                        "title": "Font color",
+                }
+            }
+        };
+
+        var properties = schema["schema"]["properties"];
+        properties["stack"] = {
+            "title": "Stacking",
+            "type": "boolean",
+            "default": false
+        };
+        if (chartType === 'graph') {
+            properties["smoothLines"] = {
+                "title": "Display smooth (curved) lines",
+                "type": "boolean",
+                "default": false
+            };
+        }
+        if (chartType === 'bar') {
+            properties["defaultBarWidth"] = {
+                "title": "Default bar width for non-aggregated data (milliseconds)",
+                "type": "number",
+                "default": 600
+            };
+        }
+        properties["shadowSize"] = {
+            "title": "Shadow size",
+            "type": "number",
+            "default": 4
+        };
+        properties["fontColor"] =  {
+            "title": "Font color",
+            "type": "string",
+            "default": "#545454"
+        };
+        properties["fontSize"] = {
+            "title": "Font size",
+            "type": "number",
+            "default": 10
+        };
+        properties["tooltipIndividual"] = {
+            "title": "Hover individual points",
+            "type": "boolean",
+            "default": false
+        };
+        properties["tooltipCumulative"] = {
+            "title": "Show cumulative values in stacking mode",
+            "type": "boolean",
+            "default": false
+        };
+        properties["tooltipValueFormatter"] = {
+            "title": "Tooltip value format function, f(value)",
+            "type": "string",
+            "default": ""
+        };
+
+        properties["grid"] = {
+            "title": "Grid settings",
+                "type": "object",
+                "properties": {
+                "color": {
+                    "title": "Primary color",
                         "type": "string",
                         "default": "#545454"
-                    },
-                    "fontSize": {
-                        "title": "Font size",
+                },
+                "backgroundColor": {
+                    "title": "Background color",
+                        "type": "string",
+                        "default": null
+                },
+                "tickColor": {
+                    "title": "Ticks color",
+                        "type": "string",
+                        "default": "#DDDDDD"
+                },
+                "outlineWidth": {
+                    "title": "Grid outline/border width (px)",
                         "type": "number",
-                        "default": 10
-                    },
-                    "tooltipIndividual": {
-                        "title": "Hover individual points",
+                        "default": 1
+                },
+                "verticalLines": {
+                    "title": "Show vertical lines",
                         "type": "boolean",
-                        "default": false
-                    },
-                    "tooltipCumulative": {
-                        "title": "Show cumulative values in stacking mode",
+                        "default": true
+                },
+                "horizontalLines": {
+                    "title": "Show horizontal lines",
                         "type": "boolean",
-                        "default": false
-                    },
-                    "tooltipValueFormatter": {
-                        "title": "Tooltip value format function, f(value)",
-                        "type": "string",
-                        "default": ""
-                    },
-                    "grid": {
-                        "title": "Grid settings",
-                        "type": "object",
-                        "properties": {
-                            "color": {
-                                "title": "Primary color",
-                                "type": "string",
-                                "default": "#545454"
-                            },
-                            "backgroundColor": {
-                                "title": "Background color",
-                                "type": "string",
-                                "default": null
-                            },
-                            "tickColor": {
-                                "title": "Ticks color",
-                                "type": "string",
-                                "default": "#DDDDDD"
-                            },
-                            "outlineWidth": {
-                                "title": "Grid outline/border width (px)",
-                                "type": "number",
-                                "default": 1
-                            },
-                            "verticalLines": {
-                                "title": "Show vertical lines",
-                                "type": "boolean",
-                                "default": true
-                            },
-                            "horizontalLines": {
-                                "title": "Show horizontal lines",
-                                "type": "boolean",
-                                "default": true
-                            }
-                        }
-                    },
-                    "xaxis": {
-                        "title": "X axis settings",
-                        "type": "object",
-                        "properties": {
-                            "showLabels": {
-                                "title": "Show labels",
-                                "type": "boolean",
-                                "default": true
-                            },
-                            "title": {
-                                "title": "Axis title",
-                                "type": "string",
-                                "default": null
-                            },
-                            "titleAngle": {
-                                "title": "Axis title's angle in degrees",
-                                "type": "number",
-                                "default": 0
-                            },
-                            "color": {
-                                "title": "Ticks color",
-                                "type": "string",
-                                "default": null
-                            }
-                        }
-                    },
-                    "yaxis": {
-                        "title": "Y axis settings",
-                        "type": "object",
-                        "properties": {
-                            "min": {
-                                "title": "Minimum value on the scale",
-                                "type": "number",
-                                "default": null
-                            },
-                            "max": {
-                                "title": "Maximum value on the scale",
-                                "type": "number",
-                                "default": null
-                            },
-                            "showLabels": {
-                                "title": "Show labels",
-                                "type": "boolean",
-                                "default": true
-                            },
-                            "title": {
-                                "title": "Axis title",
-                                "type": "string",
-                                "default": null
-                            },
-                            "titleAngle": {
-                                "title": "Axis title's angle in degrees",
-                                "type": "number",
-                                "default": 0
-                            },
-                            "color": {
-                                "title": "Ticks color",
-                                "type": "string",
-                                "default": null
-                            },
-                            "ticksFormatter": {
-                                "title": "Ticks formatter function, f(value)",
-                                "type": "string",
-                                "default": ""
-                            },
-                            "tickDecimals": {
-                                "title": "The number of decimals to display",
-                                "type": "number",
-                                "default": 0
-                            },
-                            "tickSize": {
-                                "title": "Step size between ticks",
-                                "type": "number",
-                                "default": null
-                            }
-                        }
-                    }
+                        "default": true
+                }
+            }
+        };
+
+        properties["xaxis"] = {
+            "title": "X axis settings",
+            "type": "object",
+            "properties": {
+                "showLabels": {
+                    "title": "Show labels",
+                    "type": "boolean",
+                    "default": true
                 },
-                "required": []
-            },
-            "form": [
-                "stack",
-                "smoothLines",
-                "shadowSize",
+                "title": {
+                    "title": "Axis title",
+                    "type": "string",
+                    "default": null
+                },
+                "titleAngle": {
+                    "title": "Axis title's angle in degrees",
+                    "type": "number",
+                    "default": 0
+                },
+                "color": {
+                    "title": "Ticks color",
+                    "type": "string",
+                    "default": null
+                }
+            }
+        };
+
+        properties["yaxis"] = {
+            "title": "Y axis settings",
+            "type": "object",
+            "properties": {
+                "min": {
+                    "title": "Minimum value on the scale",
+                    "type": "number",
+                    "default": null
+                },
+                "max": {
+                    "title": "Maximum value on the scale",
+                    "type": "number",
+                    "default": null
+                },
+                "showLabels": {
+                    "title": "Show labels",
+                    "type": "boolean",
+                    "default": true
+                },
+                "title": {
+                    "title": "Axis title",
+                    "type": "string",
+                    "default": null
+                },
+                "titleAngle": {
+                    "title": "Axis title's angle in degrees",
+                    "type": "number",
+                    "default": 0
+                },
+                "color": {
+                    "title": "Ticks color",
+                    "type": "string",
+                    "default": null
+                },
+                "ticksFormatter": {
+                    "title": "Ticks formatter function, f(value)",
+                    "type": "string",
+                    "default": ""
+                },
+                "tickDecimals": {
+                    "title": "The number of decimals to display",
+                    "type": "number",
+                    "default": 0
+                },
+                "tickSize": {
+                    "title": "Step size between ticks",
+                    "type": "number",
+                    "default": null
+                }
+            }
+        };
+
+        schema["schema"]["required"] = [];
+        schema["form"] = ["stack"];
+        if (chartType === 'graph') {
+            schema["form"].push("smoothLines");
+        }
+        if (chartType === 'bar') {
+            schema["form"].push("defaultBarWidth");
+        }
+        schema["form"].push("shadowSize");
+        schema["form"].push({
+            "key": "fontColor",
+            "type": "color"
+        });
+        schema["form"].push("fontSize");
+        schema["form"].push("tooltipIndividual");
+        schema["form"].push("tooltipCumulative");
+        schema["form"].push({
+            "key": "tooltipValueFormatter",
+            "type": "javascript"
+        });
+        schema["form"].push({
+            "key": "grid",
+            "items": [
                 {
-                    "key": "fontColor",
+                    "key": "grid.color",
                     "type": "color"
                 },
-                "fontSize",
-                "tooltipIndividual",
-                "tooltipCumulative",
                 {
-                    "key": "tooltipValueFormatter",
-                    "type": "javascript"
+                    "key": "grid.backgroundColor",
+                    "type": "color"
                 },
                 {
-                    "key": "grid",
-                    "items": [
-                        {
-                            "key": "grid.color",
-                            "type": "color"
-                        },
-                        {
-                            "key": "grid.backgroundColor",
-                            "type": "color"
-                        },
-                        {
-                            "key": "grid.tickColor",
-                            "type": "color"
-                        },
-                        "grid.outlineWidth",
-                        "grid.verticalLines",
-                        "grid.horizontalLines"
-                    ]
+                    "key": "grid.tickColor",
+                    "type": "color"
                 },
+                "grid.outlineWidth",
+                "grid.verticalLines",
+                "grid.horizontalLines"
+            ]
+        });
+        schema["form"].push({
+            "key": "xaxis",
+            "items": [
+                "xaxis.showLabels",
+                "xaxis.title",
+                "xaxis.titleAngle",
                 {
-                    "key": "xaxis",
-                    "items": [
-                        "xaxis.showLabels",
-                        "xaxis.title",
-                        "xaxis.titleAngle",
-                        {
-                            "key": "xaxis.color",
-                            "type": "color"
-                        }
-                    ]
+                    "key": "xaxis.color",
+                    "type": "color"
+                }
+            ]
+        });
+        schema["form"].push({
+            "key": "yaxis",
+            "items": [
+                "yaxis.min",
+                "yaxis.max",
+                "yaxis.tickDecimals",
+                "yaxis.tickSize",
+                "yaxis.showLabels",
+                "yaxis.title",
+                "yaxis.titleAngle",
+                {
+                    "key": "yaxis.color",
+                    "type": "color"
                 },
                 {
-                    "key": "yaxis",
-                    "items": [
-                        "yaxis.min",
-                        "yaxis.max",
-                        "yaxis.tickDecimals",
-                        "yaxis.tickSize",
-                        "yaxis.showLabels",
-                        "yaxis.title",
-                        "yaxis.titleAngle",
-                        {
-                            "key": "yaxis.color",
-                            "type": "color"
-                        },
-                        {
-                            "key": "yaxis.ticksFormatter",
-                            "type": "javascript"
-                        }
-                    ]
+                    "key": "yaxis.ticksFormatter",
+                    "type": "javascript"
                 }
-
             ]
-        }
+        });
+        return schema;
     }
 
     static get pieDatakeySettingsSchema() {
diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss
index bf2d3e0..722275f 100644
--- a/ui/src/app/widget/lib/rpc/knob.scss
+++ b/ui/src/app/widget/lib/rpc/knob.scss
@@ -37,8 +37,6 @@ $background-color: #e6e7e8 !default;
     position: relative;
 
     &[draggable] {
-      -moz-user-select: none;
-      -webkit-user-select: none;
       user-select: none;
     }
 
diff --git a/ui/src/app/widget/lib/rpc/led-indicator.scss b/ui/src/app/widget/lib/rpc/led-indicator.scss
index b1da5c6..3083e72 100644
--- a/ui/src/app/widget/lib/rpc/led-indicator.scss
+++ b/ui/src/app/widget/lib/rpc/led-indicator.scss
@@ -60,19 +60,11 @@ $background-color: #e6e7e8 !default;
     .led {
       position: relative;
       cursor: pointer;
-      background-image: -owg-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
-      background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
-      background-image: -moz-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
-      background-image: -o-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
       background-image: radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25));
       border-radius: 50%;
       transition: background-color .5s, box-shadow .5s;
 
       &.disabled {
-        background-image: -owg-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
-        background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
-        background-image: -moz-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
-        background-image: -o-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
         background-image: radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1));
       }
     }
diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss
index 2e37f63..c89b26b 100644
--- a/ui/src/app/widget/lib/rpc/round-switch.scss
+++ b/ui/src/app/widget/lib/rpc/round-switch.scss
@@ -59,6 +59,8 @@ $background-color: #e6e7e8 !default;
 
     .switch {
       position: relative;
+
+      box-sizing: border-box;
       width: 260px;
       min-width: 260px;
       height: 260px;
@@ -69,21 +71,14 @@ $background-color: #e6e7e8 !default;
 
       color: #424242;
       cursor: pointer;
-      -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd);
       background: #ddd;
-      background: -owg-linear-gradient(270deg, #bbb, #ddd);
-      background: -webkit-linear-gradient(270deg, #bbb, #ddd);
-      background: -moz-linear-gradient(270deg, #bbb, #ddd);
-      background: -o-linear-gradient(270deg, #bbb, #ddd);
       background: linear-gradient(180deg, #bbb, #ddd);
       border-radius: 130px;
 
-      @include box-sizing(border-box);
-
-      @include box-shadow(
-        0 0 0 8px rgba(0,0,0,.1)
-        ,0 0 3px 1px rgba(0,0,0,.1)
-        ,inset 0 8px  3px -8px rgba(255,255,255,.4));
+      box-shadow:
+        0 0 0 8px rgba(0, 0, 0, .1),
+        0 0 3px 1px rgba(0, 0, 0, .1),
+        inset 0 8px  3px -8px rgba(255, 255, 255, .4);
 
       input {
         display: none;
@@ -95,7 +90,7 @@ $background-color: #e6e7e8 !default;
         width: 100%;
         text-align: center;
 
-        @include text-shadow(1px 1px 4px #4a4a4a);
+        text-shadow: 1px 1px 4px #4a4a4a;
       }
 
       .on {
@@ -103,15 +98,15 @@ $background-color: #e6e7e8 !default;
         font-family: sans-serif;
         color: #444;
 
-        @include transition(all .1s);
+        transition: all .1s;
       }
 
       .off {
         bottom: 5px;
 
-        @include transition(all .1s);
+        transition: all .1s;
 
-        @include transform(scaleY(.85));
+        transform: scaleY(.85);
       }
 
       .but {
@@ -125,90 +120,82 @@ $background-color: #e6e7e8 !default;
         border-bottom-width: 0;
         border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px;
 
-        @include box-shadow(inset 8px 6px 5px -7px #a2a2a2,
-        inset -8px 6px 5px -7px #a2a2a2,
-        inset 0 -3px 2px -2px rgba(200, 200, 200, .5),
-        0 3px 3px -2px #fff,
-        inset 0 -230px 60px -200px rgba(255, 255, 255, .2),
-        inset 0 220px 40px -200px rgba(0, 0, 0, .3));
+        box-shadow:
+          inset 8px 6px 5px -7px #a2a2a2,
+          inset -8px 6px 5px -7px #a2a2a2,
+          inset 0 -3px 2px -2px rgba(200, 200, 200, .5),
+          0 3px 3px -2px #fff,
+          inset 0 -230px 60px -200px rgba(255, 255, 255, .2),
+          inset 0 220px 40px -200px rgba(0, 0, 0, .3);
 
-        @include transition(all .2s);
+        transition: all .2s;
       }
 
       .back {
+
+        box-sizing: border-box;
         width: 210px;
         height: 210px;
         padding: 4px 4px;
         cursor: pointer;
         background-color: #888787;
-        background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
-        background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
         background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%);
         border-radius: 105px;
 
-        @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, .3),
-        -30px 30px 30px -20px rgba(58, 58, 58, .3),
-        0 30px 30px 0 rgba(16, 16, 16, .3),
-        inset 0 -1px 0 0 #484848);
-
-        @include box-sizing(border-box);
+        box-shadow:
+          30px 30px 30px -20px rgba(58, 58, 58, .3),
+          -30px 30px 30px -20px rgba(58, 58, 58, .3),
+          0 30px 30px 0 rgba(16, 16, 16, .3),
+          inset 0 -1px 0 0 #484848;
 
-        @include transition(all .2s);
+        transition: all .2s;
       }
 
 
       input:checked + .back .on,
       input:checked + .back .off{
-        @include text-shadow(1px 1px 4px #4a4a4a);
+        text-shadow: 1px 1px 4px #4a4a4a;
       }
 
       input:checked + .back .on{
         top: 10px;
         color: #4c4c4c;
 
-        @include transform(scaleY(.85));
+        transform: scaleY(.85);
       }
 
       input:checked + .back .off{
         bottom: 5px;
         color: #444;
 
-        @include transform(scaleY(1));
+        transform: scaleY(1);
       }
 
       input:checked + .back .but{
         margin-top: 20px;
         background: #dcdcdc;
-        background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
-        background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
-        background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
-        background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
         background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent);
         border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px;
 
-        @include box-shadow(inset 8px -4px 5px -7px #a9a9a9,
-        inset -8px -4px 5px -7px #808080,
-        0 -3px 8px -4px rgba(50, 50, 50, .4),
-        inset 0 3px 4px -2px #9c9c9c,
-        inset 0 280px 40px -200px rgba(0, 0, 0, .2),
-        inset 0 -200px 40px -200px rgba(180, 180, 180, .2));
+        box-shadow:
+          inset 8px -4px 5px -7px #a9a9a9,
+          inset -8px -4px 5px -7px #808080,
+          0 -3px 8px -4px rgba(50, 50, 50, .4),
+          inset 0 3px 4px -2px #9c9c9c,
+          inset 0 280px 40px -200px rgba(0, 0, 0, .2),
+          inset 0 -200px 40px -200px rgba(180, 180, 180, .2);
       }
 
       input:checked + .back{
         padding: 2px 4px;
 
-        background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
-        background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
         background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%);
 
-        @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, .1),
-        -30px 30px 30px -20px rgba(111, 111, 111, .1),
-        0 30px 30px 0 rgba(0, 0, 0, .2),
-        inset 0 1px 2px 0 rgba(167, 167, 167, .6));
+        box-shadow:
+          30px 30px 30px -20px rgba(49, 49, 49, .1),
+          -30px 30px 30px -20px rgba(111, 111, 111, .1),
+          0 30px 30px 0 rgba(0, 0, 0, .2),
+          inset 0 1px 2px 0 rgba(167, 167, 167, .6);
       }
     }
   }
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.js b/ui/src/app/widget/lib/timeseries-table-widget.js
index bca7956..d025c1e 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.js
+++ b/ui/src/app/widget/lib/timeseries-table-widget.js
@@ -217,7 +217,9 @@ function TimeseriesTableWidgetController($element, $scope, $filter, $timeout) {
                     content = strContent;
                 }
             } else {
-                content = vm.ctx.utils.formatValue(value, contentInfo.decimals, contentInfo.units);
+                var decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : vm.widgetConfig.decimals;
+                var units = contentInfo.units || vm.widgetConfig.units;
+                content = vm.ctx.utils.formatValue(value, decimals, units, true);
             }
             return content;
         }
diff --git a/ui/src/app/widget/widget-editor.scss b/ui/src/app/widget/widget-editor.scss
index cd374e3..0eb37af 100644
--- a/ui/src/app/widget/widget-editor.scss
+++ b/ui/src/app/widget/widget-editor.scss
@@ -19,7 +19,7 @@ $edit-toolbar-height: 40px !default;
 
 .tb-editor {
   .tb-split {
-    @include box-sizing(border-box);
+    box-sizing: border-box;
     overflow-x: hidden;
     overflow-y: auto;
   }
diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss
index 4ecfc34..61b9bac 100644
--- a/ui/src/scss/animations.scss
+++ b/ui/src/scss/animations.scss
@@ -15,34 +15,34 @@
  */
 @import "~compass-sass-mixins/lib/animate";
 
-@include keyframes(tbMoveFromTopFade) {
+@keyframes tbMoveFromTopFade {
   from {
     opacity: 0;
 
-    @include transform(translate(0, -100%));
+    transform: translate(0, -100%);
   }
 }
 
-@include keyframes(tbMoveToTopFade) {
+@keyframes tbMoveToTopFade {
   to {
     opacity: 0;
 
-    @include transform(translate(0, -100%));
+    transform: translate(0, -100%);
   }
 }
 
-@include keyframes(tbMoveFromBottomFade) {
+@keyframes tbMoveFromBottomFade {
   from {
     opacity: 0;
 
-    @include transform(translate(0, 100%));
+    transform: translate(0, 100%);
   }
 }
 
-@include keyframes(tbMoveToBottomFade) {
+@keyframes tbMoveToBottomFade {
   to {
     opacity: 0;
 
-    @include transform(translate(0, 150%));
+    transform: translate(0, 150%);
   }
 }

ui/src/scss/main.scss 28(+11 -17)

diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 27c2d3b..c1a437a 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -42,7 +42,7 @@ textarea {
   word-wrap: normal;
   white-space: nowrap;
   direction: ltr;
-  -webkit-font-feature-settings: "liga";
+  -webkit-font-feature-settings: "liga"; /* stylelint-disable-line property-no-vendor-prefix */
 }
 
 a {
@@ -51,7 +51,7 @@ a {
   text-decoration: none;
   border-bottom: 1px solid rgba(64, 84, 178, .25);
 
-  @include transition(border-bottom .35s);
+  transition: border-bottom .35s;
 }
 
 a:hover,
@@ -258,13 +258,7 @@ label {
 }
 
 .tb-noselect {
-  -webkit-touch-callout: none; /* iOS Safari */
-  -webkit-user-select: none; /* Safari */
-  -khtml-user-select: none; /* Konqueror HTML */
-  -moz-user-select: none; /* Firefox */
-  -ms-user-select: none; /* Internet Explorer/Edge */
-  user-select: none; /* Non-prefixed version, currently
-                                  supported by Chrome and Opera */
+  user-select: none;
 }
 
 .tb-readonly-label {
@@ -556,7 +550,7 @@ $previewSize: 100px !default;
 }
 
 .tb-error-message.ng-animate {
-  @include transition(all .3s cubic-bezier(.55, 0, .55, .2));
+  transition: all .3s cubic-bezier(.55, 0, .55, .2);
 }
 
 .tb-error-message.ng-enter-prepare,
@@ -652,13 +646,13 @@ section.tb-top-header-buttons {
 }
 
 .tb-header-buttons .tb-btn-header {
-  @include animation(tbMoveFromTopFade .3s ease both);
   position: relative !important;
   display: inline-block !important;
+  animation: tbMoveFromTopFade .3s ease both;
 }
 
 .tb-header-buttons .tb-btn-header.ng-hide {
-  @include animation(tbMoveToTopFade .3s ease both);
+  animation: tbMoveToTopFade .3s ease both;
 }
 
 /***********************
@@ -669,24 +663,24 @@ section.tb-footer-buttons {
   position: fixed;
   right: 20px;
   bottom: 20px;
-  z-index: 13;
+  z-index: 30;
   pointer-events: none;
 }
 
 .tb-footer-buttons .tb-btn-footer {
-  @include animation(tbMoveFromBottomFade .3s ease both);
   position: relative !important;
   display: inline-block !important;
+  animation: tbMoveFromBottomFade .3s ease both;
 }
 
 .tb-footer-buttons .tb-btn-footer.ng-hide {
-  @include animation(tbMoveToBottomFade .3s ease both);
+  animation: tbMoveToBottomFade .3s ease both;
 }
 
 ._md-toast-open-bottom .tb-footer-buttons {
-  @include transition(all .4s cubic-bezier(.25, .8, .25, 1));
+  transition: all .4s cubic-bezier(.25, .8, .25, 1);
 
-  @include transform(translate3d(0, -42px, 0));
+  transform: translate3d(0, -42px, 0);
 }
 
 /***********************
diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss
index a754538..4d2c604 100644
--- a/ui/src/scss/mixins.scss
+++ b/ui/src/scss/mixins.scss
@@ -15,6 +15,7 @@
  */
 @import "~compass-sass-mixins/lib/compass";
 
+/* stylelint-disable selector-no-vendor-prefix */
 @mixin input-placeholder {
   // replaces compass/css/user-interface/input-placeholder()
 
@@ -36,6 +37,7 @@
     @content;
   }
 }
+/* stylelint-enable selector-no-vendor-prefix */
 
 @mixin line-clamp($numLines: 1, $lineHeight: 1.412) {
   position: relative;