thingsboard-aplcache

Details

diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index fe65fb5..2f63967 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -317,6 +317,13 @@ public abstract class BaseController {
     private void checkDashboard(Dashboard dashboard) throws ThingsboardException {
         checkNotNull(dashboard);
         checkTenantId(dashboard.getTenantId());
+        SecurityUser authUser = getCurrentUser();
+        if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
+            if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+                throw new ThingsboardException("You don't have permission to perform this operation!",
+                        ThingsboardErrorCode.PERMISSION_DENIED);
+            }
+        }
         if (dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
             checkCustomerId(dashboard.getCustomerId());
         }
diff --git a/dao/src/main/resources/system-data.cql b/dao/src/main/resources/system-data.cql
index 4083478..76ffd24 100644
--- a/dao/src/main/resources/system-data.cql
+++ b/dao/src/main/resources/system-data.cql
@@ -79,10 +79,15 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'label_widget',
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
 VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table',
-'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"<md-tabs md-selected=\"sourceIndex\" ng-class=\"{''tb-headless'': sources.length === 1}\"\n    id=\"tabs\" md-border-bottom flex class=\"tb-absolute-fill\">\n    <md-tab ng-repeat=\"source in sources\" label=\"{{ source.label }}\">\n        <md-table-container>\n            <table md-table>\n                <thead md-head md-order=\"source.query.order\" md-on-reorder=\"onReorder(source)\">\n                    <tr md-row>\n                        <th ng-show=\"showTimestamp\" md-column md-order-by=\"0\"><span>Timestamp</span></th>\n                        <th md-column md-order-by=\"{{ h.index }}\" ng-repeat=\"h in source.ts.header\"><span>{{ h.label }}</span></th>\n                    </tr>\n                </thead>\n                <tbody md-body>\n                    <tr md-row ng-repeat=\"row in source.ts.data\">\n                        <td ng-show=\"$index > 0 || ($index === 0 && showTimestamp)\" md-cell ng-repeat=\"d in row track by $index\" ng-style=\"cellStyle(source, $index, d)\" ng-bind-html=\"cellContent(source, $index, row, d)\">\n                        </td>\n                    </tr>    \n                </tbody>    \n            </table>\n        </md-table-container>\n        <md-table-pagination md-limit=\"source.query.limit\" md-limit-options=\"[5, 10, 15]\"\n                             md-page=\"source.query.page\" md-total=\"{{source.ts.count}}\"\n                             md-on-paginate=\"onPaginate(source)\" md-page-select>\n        </md-table-pagination>\n    </md-tab>\n</md-tabs>","templateCss":"table.md-table thead.md-head>tr.md-row {\n    height: 40px;\n}\n\ntable.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {\n    height: 38px;\n}\n\n.md-table-pagination>* {\n    height: 46px;\n}\n","controllerScript":"var filter;\n\nfns.init = function(containerElement, settings, datasources,\n    data, scope) {\n    \n    filter = scope.$injector.get(\"$filter\");\n    \n    scope.sources = [];\n    scope.sourceIndex = 0;\n    scope.showTimestamp = settings.showTimestamp !== false;\n    \n    var keyOffset = 0;\n    for (var ds in datasources) {\n        var source = {};\n        var datasource = datasources[ds];\n        source.keyStartIndex = keyOffset;\n        keyOffset += datasource.dataKeys.length;\n        source.keyEndIndex = keyOffset;\n        source.label = datasource.name;\n        source.data = [];\n        source.rawData = [];\n        source.query = {\n            limit: 5,\n            page: 1,\n            order: ''-0''\n        }\n        source.ts = {\n            header: [],\n            count: 0,\n            data: [],\n            stylesInfo: [],\n            contentsInfo: [],\n            rowDataTemplate: {}\n        }\n        source.ts.rowDataTemplate[''Timestamp''] = null;\n        for (var a = 0; a < datasource.dataKeys.length; a++ ) {\n            var dataKey = datasource.dataKeys[a];\n            var keySettings = dataKey.settings;\n            source.ts.header.push({\n                index: a+1,\n                label: dataKey.label\n            });\n            source.ts.rowDataTemplate[dataKey.label] = null;\n\n            var cellStyleFunction = null;\n            var useCellStyleFunction = false;\n            \n            if (keySettings.useCellStyleFunction === true) {\n                if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {\n                    try {\n                       cellStyleFunction = new Function(''value'', keySettings.cellStyleFunction);\n                       useCellStyleFunction = true;\n                    } catch (e) {\n                       cellStyleFunction = null;\n                       useCellStyleFunction = false;\n                    }\n                }\n            }\n\n            source.ts.stylesInfo.push({\n                useCellStyleFunction: useCellStyleFunction,\n                cellStyleFunction: cellStyleFunction\n            });\n            \n            var cellContentFunction = null;\n            var useCellContentFunction = false;\n            \n            if (keySettings.useCellContentFunction === true) {\n                if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {\n                    try {\n                       cellContentFunction = new Function(''value, rowData, filter'', keySettings.cellContentFunction);\n                       useCellContentFunction = true;\n                    } catch (e) {\n                       cellContentFunction = null;\n                       useCellContentFunction = false;\n                    }\n                }\n            }\n            \n            source.ts.contentsInfo.push({\n                useCellContentFunction: useCellContentFunction,\n                cellContentFunction: cellContentFunction\n            });\n            \n        }\n        scope.sources.push(source);\n    }\n\n    scope.onPaginate = function(source) {\n        updatePage(source);\n    }\n    \n    scope.onReorder = function(source) {\n        reorder(source);\n        updatePage(source);\n    }\n    \n    scope.cellStyle = function(source, index, value) {\n        var style = {};\n        if (index > 0) {\n            var styleInfo = source.ts.stylesInfo[index-1];\n            if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {\n                try {\n                    style = styleInfo.cellStyleFunction(value);\n                } catch (e) {\n                    style = {};\n                }\n            }\n        }\n        return style;\n    }\n\n    scope.cellContent = function(source, index, row, value) {\n        if (index === 0) {\n            return filter(''date'')(value, ''yyyy-MM-dd HH:mm:ss'');\n        } else {\n            var strContent = '''';\n            if (angular.isDefined(value)) {\n                strContent = ''''+value;\n            }\n            var content = strContent;\n            var contentInfo = source.ts.contentsInfo[index-1];\n            if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {\n                try {\n                    var rowData = source.ts.rowDataTemplate;\n                    rowData[''Timestamp''] = row[0];\n                    for (var h in source.ts.header) {\n                        var headerInfo = source.ts.header[h];\n                        rowData[headerInfo.label] = row[headerInfo.index];\n                    }\n                    content = contentInfo.cellContentFunction(value, rowData, filter);\n                } catch (e) {\n                    content = strContent;\n                }\n            }            \n            return content;\n        }\n    }\n    \n    scope.$watch(''sourceIndex'', function(newIndex, oldIndex) {\n       if (newIndex != oldIndex) {\n           updateSourceData(scope.sources[scope.sourceIndex]);\n       } \n    });\n    \n    scope.$apply();\n}\n\nfunction updatePage(source) {\n    var startIndex = source.query.limit * (source.query.page - 1);\n    source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);\n}\n\nfunction reorder(source) {\n    source.data = filter(''orderBy'')(source.data, source.query.order);\n}\n\nfunction convertData(data) {\n    var rowsMap = [];\n    for (var d = 0; d < data.length; d++) {\n        var columnData = data[d].data;\n        for (var i = 0; i < columnData.length; i++) {\n            var cellData = columnData[i];\n            var timestamp = cellData[0];\n            var row = rowsMap[timestamp];\n            if (!row) {\n                row = [];\n                row[0] = timestamp;\n                for (var c = 0; c < data.length; c++) {\n                    row[c+1] = null;\n                }\n                rowsMap[timestamp] = row;\n            }\n            row[d+1] = cellData[1];\n        }\n    }\n    var rows = [];\n    for (var t in rowsMap) {\n        rows.push(rowsMap[t]);\n    }\n    return rows;\n}\n\nfunction updateSourceData(source) {\n    source.data = convertData(source.rawData);\n    source.ts.count = source.data.length;\n    reorder(source);\n    updatePage(source);\n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged, scope) {\n    for (var s in scope.sources) {\n        var source = scope.sources[s];\n        source.rawData = data.slice(source.keyStartIndex, source.keyEndIndex);\n    }\n    updateSourceData(scope.sources[scope.sourceIndex]);\n    scope.$apply();\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"TimeseriesTableSettings\",\n        \"properties\": {\n            \"showTimestamp\": {\n                \"title\": \"Display timestamp column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"showTimestamp\"\n    ]\n}","dataKeySettingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"useCellStyleFunction\": {\n                \"title\": \"Use cell style function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellStyleFunction\": {\n                \"title\": \"Cell style function: f(value)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"useCellContentFunction\": {\n                \"title\": \"Use cell content function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellContentFunction\": {\n                \"title\": \"Cell content function: f(value, rowData, filter)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"useCellStyleFunction\",\n        {\n            \"key\": \"cellStyleFunction\",\n            \"type\": \"javascript\"\n        },\n        \"useCellContentFunction\",\n        {\n            \"key\": \"cellContentFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature  °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = (value + 60)/120 * 100;\\n    var color = tinycolor.mix(''blue'', ''red'', amount = percent);\\n    color.setAlpha(.5);\\n    return {\\n      paddingLeft: ''20px'',\\n      color: ''#ffffff'',\\n      background: color.toRgbString(),\\n      fontSize: ''18px''\\n    };\\n} else {\\n    return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = value;\\n    var backgroundColor = tinycolor(''blue'');\\n    backgroundColor.setAlpha(value/100);\\n    var color = ''blue'';\\n    if (value > 50) {\\n        color = ''white'';\\n    }\\n    \\n    return {\\n      paddingLeft: ''20px'',\\n      color: color,\\n      background: backgroundColor.toRgbString(),\\n      fontSize: ''18px''\\n    };\\n} else {\\n    return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\"}"}',
+'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"<md-tabs md-selected=\"sourceIndex\" ng-class=\"{''tb-headless'': sources.length === 1}\"\n    id=\"tabs\" md-border-bottom flex class=\"tb-absolute-fill\">\n    <md-tab ng-repeat=\"source in sources\" label=\"{{ source.label }}\">\n        <md-table-container>\n            <table md-table>\n                <thead md-head md-order=\"source.query.order\" md-on-reorder=\"onReorder(source)\">\n                    <tr md-row>\n                        <th ng-show=\"showTimestamp\" md-column md-order-by=\"0\"><span>Timestamp</span></th>\n                        <th md-column md-order-by=\"{{ h.index }}\" ng-repeat=\"h in source.ts.header\"><span>{{ h.label }}</span></th>\n                    </tr>\n                </thead>\n                <tbody md-body>\n                    <tr md-row ng-repeat=\"row in source.ts.data\">\n                        <td ng-show=\"$index > 0 || ($index === 0 && showTimestamp)\" md-cell ng-repeat=\"d in row track by $index\" ng-style=\"cellStyle(source, $index, d)\" ng-bind-html=\"cellContent(source, $index, row, d)\">\n                        </td>\n                    </tr>    \n                </tbody>    \n            </table>\n        </md-table-container>\n        <md-table-pagination md-limit=\"source.query.limit\" md-limit-options=\"[5, 10, 15]\"\n                             md-page=\"source.query.page\" md-total=\"{{source.ts.count}}\"\n                             md-on-paginate=\"onPaginate(source)\" md-page-select>\n        </md-table-pagination>\n    </md-tab>\n</md-tabs>","templateCss":"table.md-table thead.md-head>tr.md-row {\n    height: 40px;\n}\n\ntable.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {\n    height: 38px;\n}\n\n.md-table-pagination>* {\n    height: 46px;\n}\n","controllerScript":"var filter;\n\nfns.init = function(containerElement, settings, datasources,\n    data, scope) {\n    \n    filter = scope.$injector.get(\"$filter\");\n    \n    scope.sources = [];\n    scope.sourceIndex = 0;\n    scope.showTimestamp = settings.showTimestamp !== false;\n    \n    var keyOffset = 0;\n    for (var ds in datasources) {\n        var source = {};\n        var datasource = datasources[ds];\n        source.keyStartIndex = keyOffset;\n        keyOffset += datasource.dataKeys.length;\n        source.keyEndIndex = keyOffset;\n        source.label = datasource.name;\n        source.data = [];\n        source.rawData = [];\n        source.query = {\n            limit: 5,\n            page: 1,\n            order: ''-0''\n        }\n        source.ts = {\n            header: [],\n            count: 0,\n            data: [],\n            stylesInfo: [],\n            contentsInfo: [],\n            rowDataTemplate: {}\n        }\n        source.ts.rowDataTemplate[''Timestamp''] = null;\n        for (var a = 0; a < datasource.dataKeys.length; a++ ) {\n            var dataKey = datasource.dataKeys[a];\n            var keySettings = dataKey.settings;\n            source.ts.header.push({\n                index: a+1,\n                label: dataKey.label\n            });\n            source.ts.rowDataTemplate[dataKey.label] = null;\n\n            var cellStyleFunction = null;\n            var useCellStyleFunction = false;\n            \n            if (keySettings.useCellStyleFunction === true) {\n                if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {\n                    try {\n                       cellStyleFunction = new Function(''value'', keySettings.cellStyleFunction);\n                       useCellStyleFunction = true;\n                    } catch (e) {\n                       cellStyleFunction = null;\n                       useCellStyleFunction = false;\n                    }\n                }\n            }\n\n            source.ts.stylesInfo.push({\n                useCellStyleFunction: useCellStyleFunction,\n                cellStyleFunction: cellStyleFunction\n            });\n            \n            var cellContentFunction = null;\n            var useCellContentFunction = false;\n            \n            if (keySettings.useCellContentFunction === true) {\n                if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {\n                    try {\n                       cellContentFunction = new Function(''value, rowData, filter'', keySettings.cellContentFunction);\n                       useCellContentFunction = true;\n                    } catch (e) {\n                       cellContentFunction = null;\n                       useCellContentFunction = false;\n                    }\n                }\n            }\n            \n            source.ts.contentsInfo.push({\n                useCellContentFunction: useCellContentFunction,\n                cellContentFunction: cellContentFunction\n            });\n            \n        }\n        scope.sources.push(source);\n    }\n\n    scope.onPaginate = function(source) {\n        updatePage(source);\n    }\n    \n    scope.onReorder = function(source) {\n        reorder(source);\n        updatePage(source);\n    }\n    \n    scope.cellStyle = function(source, index, value) {\n        var style = {};\n        if (index > 0) {\n            var styleInfo = source.ts.stylesInfo[index-1];\n            if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {\n                try {\n                    style = styleInfo.cellStyleFunction(value);\n                } catch (e) {\n                    style = {};\n                }\n            }\n        }\n        return style;\n    }\n\n    scope.cellContent = function(source, index, row, value) {\n        if (index === 0) {\n            return filter(''date'')(value, ''yyyy-MM-dd HH:mm:ss'');\n        } else {\n            var strContent = '''';\n            if (angular.isDefined(value)) {\n                strContent = ''''+value;\n            }\n            var content = strContent;\n            var contentInfo = source.ts.contentsInfo[index-1];\n            if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {\n                try {\n                    var rowData = source.ts.rowDataTemplate;\n                    rowData[''Timestamp''] = row[0];\n                    for (var h in source.ts.header) {\n                        var headerInfo = source.ts.header[h];\n                        rowData[headerInfo.label] = row[headerInfo.index];\n                    }\n                    content = contentInfo.cellContentFunction(value, rowData, filter);\n                } catch (e) {\n                    content = strContent;\n                }\n            }            \n            return content;\n        }\n    }\n    \n    scope.$watch(''sourceIndex'', function(newIndex, oldIndex) {\n       if (newIndex != oldIndex) {\n           updateSourceData(scope.sources[scope.sourceIndex]);\n       } \n    });\n    \n    scope.$apply();\n}\n\nfunction updatePage(source) {\n    var startIndex = source.query.limit * (source.query.page - 1);\n    source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);\n}\n\nfunction reorder(source) {\n    source.data = filter(''orderBy'')(source.data, source.query.order);\n}\n\nfunction convertData(data) {\n    var rowsMap = [];\n    for (var d = 0; d < data.length; d++) {\n        var columnData = data[d].data;\n        for (var i = 0; i < columnData.length; i++) {\n            var cellData = columnData[i];\n            var timestamp = cellData[0];\n            var row = rowsMap[timestamp];\n            if (!row) {\n                row = [];\n                row[0] = timestamp;\n                for (var c = 0; c < data.length; c++) {\n                    row[c+1] = undefined;\n                }\n                rowsMap[timestamp] = row;\n            }\n            row[d+1] = cellData[1];\n        }\n    }\n    var rows = [];\n    for (var t in rowsMap) {\n        rows.push(rowsMap[t]);\n    }\n    return rows;\n}\n\nfunction updateSourceData(source) {\n    source.data = convertData(source.rawData);\n    source.ts.count = source.data.length;\n    reorder(source);\n    updatePage(source);\n}\n\nfns.redraw = function(containerElement, width, height, data,\n    timeWindow, sizeChanged, scope) {\n    for (var s in scope.sources) {\n        var source = scope.sources[s];\n        source.rawData = data.slice(source.keyStartIndex, source.keyEndIndex);\n    }\n    updateSourceData(scope.sources[scope.sourceIndex]);\n    scope.$apply();\n};\n\nfns.destroy = function() {\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"TimeseriesTableSettings\",\n        \"properties\": {\n            \"showTimestamp\": {\n                \"title\": \"Display timestamp column\",\n                \"type\": \"boolean\",\n                \"default\": true\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"showTimestamp\"\n    ]\n}","dataKeySettingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"DataKeySettings\",\n        \"properties\": {\n            \"useCellStyleFunction\": {\n                \"title\": \"Use cell style function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellStyleFunction\": {\n                \"title\": \"Cell style function: f(value)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"useCellContentFunction\": {\n                \"title\": \"Use cell content function\",\n                \"type\": \"boolean\",\n                \"default\": false\n            },\n            \"cellContentFunction\": {\n                \"title\": \"Cell content function: f(value, rowData, filter)\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"useCellStyleFunction\",\n        {\n            \"key\": \"cellStyleFunction\",\n            \"type\": \"javascript\"\n        },\n        \"useCellContentFunction\",\n        {\n            \"key\": \"cellContentFunction\",\n            \"type\": \"javascript\"\n        }\n    ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature  °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = (value + 60)/120 * 100;\\n    var color = tinycolor.mix(''blue'', ''red'', amount = percent);\\n    color.setAlpha(.5);\\n    return {\\n      paddingLeft: ''20px'',\\n      color: ''#ffffff'',\\n      background: color.toRgbString(),\\n      fontSize: ''18px''\\n    };\\n} else {\\n    return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n    var percent = value;\\n    var backgroundColor = tinycolor(''blue'');\\n    backgroundColor.setAlpha(value/100);\\n    var color = ''blue'';\\n    if (value > 50) {\\n        color = ''white'';\\n    }\\n    \\n    return {\\n      paddingLeft: ''20px'',\\n      color: color,\\n      background: backgroundColor.toRgbString(),\\n      fontSize: ''18px''\\n    };\\n} else {\\n    return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\"}"}',
 'Timeseries table' );
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
+VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'html_card',
+'{"type":"static","sizeX":7.5,"sizeY":3,"resources":[],"templateHtml":"","templateCss":"","controllerScript":"fns.init = function(containerElement, settings, datasources,\n    data) {\n\n    var container = $(containerElement);\n    \n    var cssParser = new cssjs();\n    cssParser.testMode = false;\n    var namespace = ''html-card-'' + hashCode(settings.cardCss);\n    cssParser.cssPreviewNamespace = namespace;\n    cssParser.createStyleElement(namespace, settings.cardCss);\n    container.addClass(namespace);\n    container.html(settings.cardHtml);\n    \n    function hashCode(str) {\n        var hash = 0;\n        var i, char;\n        if (str.length === 0) return hash;\n        for (i = 0; i < str.length; i++) {\n            char = str.charCodeAt(i);\n            hash = ((hash << 5) - hash) + char;\n            hash = hash & hash;\n        }\n        return hash;\n    }\n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n\n};","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"required\": [\"cardHtml\"],\n        \"properties\": {\n            \"cardCss\": {\n                \"title\": \"CSS\",\n                \"type\": \"string\",\n                \"default\": \".card {\\n font-weight: bold; \\n}\"\n            },\n            \"cardHtml\": {\n                \"title\": \"HTML\",\n                \"type\": \"string\",\n                \"default\": \"<div class=''card''>HTML code here</div>\"\n            }\n        }\n    },\n    \"form\": [\n        {\n            \"key\": \"cardCss\",\n            \"type\": \"css\"\n        },           \n        {\n            \"key\": \"cardHtml\",\n            \"type\": \"html\"\n        }    \n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"static\",\"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\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"<div class=''card''>HTML code here</div>\",\"cardCss\":\".card {\\n    font-weight: bold;\\n    font-size: 32px;\\n    color: #999;\\n    width: 100%;\\n    height: 100%;\\n    display: flex;\\n    align-items: center;\\n    justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}"}',
+'HTML Card' );
+
+INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
 VALUES ( now ( ), minTimeuuid ( 0 ), 'analogue_gauges', 'speed_gauge_canvas_gauges',
 '{"type":"latest","sizeX":7,"sizeY":5,"resources":[],"templateHtml":"<canvas id=\"radialGauge\"></canvas>\n","templateCss":"","controllerScript":"var gauge;\n\nfns.init = function(containerElement, settings, datasources,\n    data) {\n    gauge = new TbAnalogueRadialGauge(containerElement, settings, data, ''radialGauge'');    \n\n}\n\n\nfns.redraw = function(containerElement, width, height, data, timeWindow, sizeChanged) {\n    gauge.redraw(width, height, data, sizeChanged);\n};\n\nfns.destroy = function() {\n}\n","settingsSchema":"{\n    \"schema\": {\n        \"type\": \"object\",\n        \"title\": \"Settings\",\n        \"properties\": {\n            \"minValue\": {\n                \"title\": \"Minimum value\",\n                \"type\": \"number\",\n                \"default\": 0\n            },\n            \"maxValue\": {\n                \"title\": \"Maximum value\",\n                \"type\": \"number\",\n                \"default\": 100\n            },\n            \"unitTitle\": {\n                \"title\": \"Unit title\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"showUnitTitle\": {\n                \"title\": \"Show unit title\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"units\": {\n                \"title\": \"Units\",\n                \"type\": \"string\",\n                \"default\": \"\"\n            },\n            \"majorTicksCount\": {\n                \"title\": \"Major ticks count\",\n                \"type\": \"number\",\n                \"default\": null\n            },\n            \"minorTicks\": {\n                \"title\": \"Minor ticks count\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"valueBox\": {\n                \"title\": \"Show value box\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"valueInt\": {\n                \"title\": \"Digits count for integer part of value\",\n                \"type\": \"number\",\n                \"default\": 3\n            },\n            \"valueDec\": {\n                \"title\": \"Digits count for decimal part of value\",\n                \"type\": \"number\",\n                \"default\": 2\n            },\n            \"defaultColor\": {\n                \"title\": \"Default color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorPlate\": {\n                \"title\": \"Plate color\",\n                \"type\": \"string\",\n                \"default\": \"#fff\"\n            },\n            \"colorMajorTicks\": {\n                \"title\": \"Major ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#444\"\n            },\n            \"colorMinorTicks\": {\n                \"title\": \"Minor ticks color\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorNeedle\": {\n                \"title\": \"Needle color\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleEnd\": {\n                \"title\": \"Needle color - end gradient\",\n                \"type\": \"string\",\n                \"default\": null\n            },\n            \"colorNeedleShadowUp\": {\n                \"title\": \"Upper half of the needle shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(2,255,255,0.2)\"\n            },\n            \"colorNeedleShadowDown\": {\n                \"title\": \"Drop shadow needle color.\",\n                \"type\": \"string\",\n                \"default\": \"rgba(188,143,143,0.45)\"\n            },\n            \"colorValueBoxRect\": {\n                \"title\": \"Value box rectangle stroke color\",\n                \"type\": \"string\",\n                \"default\": \"#888\"\n            },\n            \"colorValueBoxRectEnd\": {\n                \"title\": \"Value box rectangle stroke color - end gradient\",\n                \"type\": \"string\",\n                \"default\": \"#666\"\n            },\n            \"colorValueBoxBackground\": {\n                \"title\": \"Value box background color\",\n                \"type\": \"string\",\n                \"default\": \"#babab2\"\n            },\n            \"colorValueBoxShadow\": {\n                \"title\": \"Value box shadow color\",\n                \"type\": \"string\",\n                \"default\": \"rgba(0,0,0,1)\"\n            },\n            \"highlights\": {\n                \"title\": \"Highlights\",\n                \"type\": \"array\",\n                \"items\": {\n                  \"title\": \"Highlight\",\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"from\": {\n                      \"title\": \"From\",\n                      \"type\": \"number\"\n                    },\n                    \"to\": {\n                      \"title\": \"To\",\n                      \"type\": \"number\"\n                    },\n                    \"color\": {\n                      \"title\": \"Color\",\n                      \"type\": \"string\"\n                    }\n                  }\n                }\n            },\n            \"highlightsWidth\": {\n                \"title\": \"Highlights width\",\n                \"type\": \"number\",\n                \"default\": 15\n            },\n            \"showBorder\": {\n                \"title\": \"Show border\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"numbersFont\": {\n                \"title\": \"Tick numbers font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 18\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": null\n                    }\n                }\n            },\n            \"titleFont\": {\n                \"title\": \"Title text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 24\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"unitsFont\": {\n                \"title\": \"Units text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 22\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#888\"\n                    }\n                }\n            },\n            \"valueFont\": {\n                \"title\": \"Value text font\",\n                \"type\": \"object\",\n                 \"properties\": {\n                    \"family\": {\n                        \"title\": \"Font family\",\n                        \"type\": \"string\",\n                        \"default\": \"RobotoDraft\"\n                    },\n                    \"size\": {\n                      \"title\": \"Size\",\n                      \"type\": \"number\",\n                      \"default\": 40\n                    },\n                    \"style\": {\n                      \"title\": \"Style\",\n                      \"type\": \"string\",\n                      \"default\": \"normal\"\n                    },\n                    \"weight\": {\n                      \"title\": \"Weight\",\n                      \"type\": \"string\",\n                      \"default\": \"500\"\n                    },\n                    \"color\": {\n                        \"title\": \"color\",\n                        \"type\": \"string\",\n                        \"default\": \"#444\"\n                    },\n                    \"shadowColor\": {\n                        \"title\": \"Shadow color\",\n                        \"type\": \"string\",\n                        \"default\": \"rgba(0,0,0,0.3)\"\n                    }\n                }\n            },\n            \"animation\": {\n                \"title\": \"Enable animation\",\n                \"type\": \"boolean\",\n                \"default\": true\n            },\n            \"animationDuration\": {\n                \"title\": \"Animation duration\",\n                \"type\": \"number\",\n                \"default\": 500\n            },\n            \"animationRule\": {\n                \"title\": \"Animation rule\",\n                \"type\": \"string\",\n                \"default\": \"cycle\"\n            },\n            \"startAngle\": {\n                \"title\": \"Start ticks angle\",\n                \"type\": \"number\",\n                \"default\": 45\n            },\n            \"ticksAngle\": {\n                \"title\": \"Ticks angle\",\n                \"type\": \"number\",\n                \"default\": 270\n            },\n            \"needleCircleSize\": {\n                \"title\": \"Needle circle size\",\n                \"type\": \"number\",\n                \"default\": 10\n            }\n        },\n        \"required\": []\n    },\n    \"form\": [\n        \"startAngle\",\n        \"ticksAngle\",\n        \"needleCircleSize\",\n        \"minValue\",\n        \"maxValue\",\n        \"unitTitle\",\n        \"showUnitTitle\",\n        \"units\",\n        \"majorTicksCount\",\n        \"minorTicks\",\n        \"valueBox\",\n        \"valueInt\",\n        \"valueDec\",\n        {\n            \"key\": \"defaultColor\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorPlate\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMajorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorMinorTicks\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedle\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowUp\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorNeedleShadowDown\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRect\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxRectEnd\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxBackground\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"colorValueBoxShadow\",\n            \"type\": \"color\"\n        },\n        {\n            \"key\": \"highlights\",\n            \"items\": [\n                \"highlights[].from\",\n                \"highlights[].to\",\n                {\n                    \"key\": \"highlights[].color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        \"highlightsWidth\",\n        \"showBorder\",\n        {\n            \"key\": \"numbersFont\",\n            \"items\": [\n                \"numbersFont.family\",\n                \"numbersFont.size\",\n                {\n                   \"key\": \"numbersFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"numbersFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"numbersFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"titleFont\",\n            \"items\": [\n                \"titleFont.family\",\n                \"titleFont.size\",\n                {\n                   \"key\": \"titleFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"titleFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"titleFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"unitsFont\",\n            \"items\": [\n                \"unitsFont.family\",\n                \"unitsFont.size\",\n                {\n                   \"key\": \"unitsFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"unitsFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"unitsFont.color\",\n                    \"type\": \"color\"\n                }\n            ]\n        },\n        {\n            \"key\": \"valueFont\",\n            \"items\": [\n                \"valueFont.family\",\n                \"valueFont.size\",\n                {\n                   \"key\": \"valueFont.style\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"italic\",\n                           \"label\": \"Italic\"\n                       },\n                       {\n                           \"value\": \"oblique\",\n                           \"label\": \"Oblique\"\n                       }\n                    ]\n                },\n                {\n                   \"key\": \"valueFont.weight\",\n                   \"type\": \"rc-select\",\n                   \"multiple\": false,\n                   \"items\": [\n                       {\n                           \"value\": \"normal\",\n                           \"label\": \"Normal\"\n                       },\n                       {\n                           \"value\": \"bold\",\n                           \"label\": \"Bold\"\n                       },\n                       {\n                           \"value\": \"bolder\",\n                           \"label\": \"Bolder\"\n                       },\n                       {\n                           \"value\": \"lighter\",\n                           \"label\": \"Lighter\"\n                       },\n                       {\n                           \"value\": \"100\",\n                           \"label\": \"100\"\n                       },\n                       {\n                           \"value\": \"200\",\n                           \"label\": \"200\"\n                       },\n                       {\n                           \"value\": \"300\",\n                           \"label\": \"300\"\n                       },\n                       {\n                           \"value\": \"400\",\n                           \"label\": \"400\"\n                       },\n                       {\n                           \"value\": \"500\",\n                           \"label\": \"500\"\n                       },\n                       {\n                           \"value\": \"600\",\n                           \"label\": \"600\"\n                       },\n                       {\n                           \"value\": \"700\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"800\",\n                           \"label\": \"800\"\n                       },\n                       {\n                           \"value\": \"900\",\n                           \"label\": \"900\"\n                       }\n                    ]\n                },\n                {\n                    \"key\": \"valueFont.color\",\n                    \"type\": \"color\"\n                },\n                {\n                    \"key\": \"valueFont.shadowColor\",\n                    \"type\": \"color\"\n                }\n            ]\n        },        \n        \"animation\",\n        \"animationDuration\",\n        {\n            \"key\": \"animationRule\",\n            \"type\": \"rc-select\",\n            \"multiple\": false,\n            \"items\": [\n                {\n                    \"value\": \"linear\",\n                    \"label\": \"Linear\"\n                },\n                {\n                    \"value\": \"quad\",\n                    \"label\": \"Quad\"\n                },\n                {\n                    \"value\": \"quint\",\n                    \"label\": \"Quint\"\n                },\n                {\n                    \"value\": \"cycle\",\n                    \"label\": \"Cycle\"\n                },\n                {\n                    \"value\": \"bounce\",\n                    \"label\": \"Bounce\"\n                },\n                {\n                    \"value\": \"elastic\",\n                    \"label\": \"Elastic\"\n                },\n                {\n                    \"value\": \"dequad\",\n                    \"label\": \"Dequad\"\n                },\n                {\n                    \"value\": \"dequint\",\n                    \"label\": \"Dequint\"\n                },\n                {\n                    \"value\": \"decycle\",\n                    \"label\": \"Decycle\"\n                },\n                {\n                    \"value\": \"debounce\",\n                    \"label\": \"Debounce\"\n                },\n                {\n                    \"value\": \"delastic\",\n                    \"label\": \"Delastic\"\n                }\n            ]\n        }\n    ]\n}","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.7282710489093589,\"funcBody\":\"var value = prevValue + Math.random() * 50 - 25;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 220) {\\n\\tvalue = 220;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"maxValue\":180,\"startAngle\":45,\"ticksAngle\":270,\"showBorder\":false,\"defaultColor\":\"#e65100\",\"needleCircleSize\":7,\"highlights\":[{\"from\":80,\"to\":120,\"color\":\"#fdd835\"},{\"color\":\"#e57373\",\"from\":120,\"to\":180}],\"showUnitTitle\":false,\"colorPlate\":\"#fff\",\"colorMajorTicks\":\"#444\",\"colorMinorTicks\":\"#666\",\"minorTicks\":2,\"valueInt\":3,\"minValue\":0,\"valueDec\":0,\"highlightsWidth\":15,\"valueBox\":true,\"animation\":true,\"animationDuration\":1500,\"animationRule\":\"linear\",\"colorNeedleShadowUp\":\"rgba(2, 255, 255, 0)\",\"colorNeedleShadowDown\":\"rgba(188, 143, 143, 0.78)\",\"units\":\"MPH\",\"majorTicksCount\":9,\"numbersFont\":{\"family\":\"RobotoDraft\",\"size\":22,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"titleFont\":{\"family\":\"RobotoDraft\",\"size\":24,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#888\"},\"unitsFont\":{\"family\":\"RobotoDraft\",\"size\":28,\"style\":\"normal\",\"weight\":\"500\",\"color\":\"#616161\"},\"valueFont\":{\"size\":32,\"style\":\"normal\",\"weight\":\"normal\",\"shadowColor\":\"rgba(0, 0, 0, 0.49)\",\"color\":\"#444\",\"family\":\"Segment7Standard\"},\"colorValueBoxRect\":\"#888\",\"colorValueBoxRectEnd\":\"#666\",\"colorValueBoxBackground\":\"#babab2\",\"colorValueBoxShadow\":\"rgba(0,0,0,1)\"},\"title\":\"Speed gauge - Canvas Gauges\"}"}',
 'Speed gauge - Canvas Gauges' );
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index 23375cb..cde32f0 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -46,6 +46,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
     $window.TbAnalogueRadialGauge = TbAnalogueRadialGauge;
     $window.TbDigitalGauge = TbDigitalGauge;
     $window.TbMapWidget = TbMapWidget;
+    $window.cssjs = cssjs;
 
     var cssParser = new cssjs();
     cssParser.testMode = false;
diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js
index f6cbc18..057b413 100644
--- a/ui/src/app/app.run.js
+++ b/ui/src/app/app.run.js
@@ -23,7 +23,10 @@ export default function AppRun($rootScope, $window, $log, $state, $mdDialog, $fi
     var unauthorizedDialog = null;
     var forbiddenDialog = null;
 
+    $rootScope.iframeMode = false;
+
     if (frame) {
+        $rootScope.iframeMode = true;
         var dataWidgetAttr = angular.element(frame).attr('data-widget');
         if (dataWidgetAttr) {
             $rootScope.editWidgetInfo = angular.fromJson(dataWidgetAttr);
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index 71bc886..5314038 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -141,6 +141,14 @@ export default angular.module('thingsboard.types', [])
                         bundleAlias: "gpio_widgets",
                         alias: "basic_gpio_control"
                     }
+                },
+                static: {
+                    value: "static",
+                    name: "widget.static",
+                    template: {
+                        bundleAlias: "cards",
+                        alias: "html_card"
+                    }
                 }
             },
             systemBundleAlias: {
diff --git a/ui/src/app/components/dashboard.directive.js b/ui/src/app/components/dashboard.directive.js
index 4fc8599..0e89a01 100644
--- a/ui/src/app/components/dashboard.directive.js
+++ b/ui/src/app/components/dashboard.directive.js
@@ -139,6 +139,7 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
     vm.widgetBackgroundColor = widgetBackgroundColor;
     vm.widgetPadding = widgetPadding;
     vm.showWidgetTitle = showWidgetTitle;
+    vm.dropWidgetShadow = dropWidgetShadow;
     vm.hasTimewindow = hasTimewindow;
     vm.editWidget = editWidget;
     vm.exportWidget = exportWidget;
@@ -521,6 +522,14 @@ function DashboardController($scope, $rootScope, $element, $timeout, $log, toast
         }
     }
 
+    function dropWidgetShadow(widget) {
+        if (angular.isDefined(widget.config.dropShadow)) {
+            return widget.config.dropShadow;
+        } else {
+            return true;
+        }
+    }
+
     function hasTimewindow(widget) {
         return widget.type === types.widgetType.timeseries.value;
     }
diff --git a/ui/src/app/components/dashboard.tpl.html b/ui/src/app/components/dashboard.tpl.html
index 111f0d3..cac3d7f 100644
--- a/ui/src/app/components/dashboard.tpl.html
+++ b/ui/src/app/components/dashboard.tpl.html
@@ -28,8 +28,10 @@
 					<li gridster-item="widget" ng-repeat="widget in vm.widgets">
 						<md-menu md-position-mode="target target" tb-mousepoint-menu>
 							<div tb-expand-fullscreen
-								        expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget md-whiteframe-4dp"
-										ng-class="{'tb-highlighted': vm.isHighlighted(widget), 'tb-not-highlighted': vm.isNotHighlighted(widget)}"
+								        expand-button-id="expand-button" on-fullscreen-changed="vm.onWidgetFullscreenChanged(expanded, widget)" layout="column" class="tb-widget"
+										ng-class="{'tb-highlighted': vm.isHighlighted(widget),
+										           'tb-not-highlighted': vm.isNotHighlighted(widget),
+										           'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}"
 										tb-mousedown="vm.widgetMouseDown($event, widget)"
 										tb-mousemove="vm.widgetMouseMove($event, widget)"
 										tb-mouseup="vm.widgetMouseUp($event, widget)"
diff --git a/ui/src/app/components/datakey-config.tpl.html b/ui/src/app/components/datakey-config.tpl.html
index 5f5b821..f170220 100644
--- a/ui/src/app/components/datakey-config.tpl.html
+++ b/ui/src/app/components/datakey-config.tpl.html
@@ -53,7 +53,7 @@
 		<br/>
 		<tb-js-func ng-model="model.funcBody"
 				 function-args="{{ ['time', 'prevValue'] }}"
-				 validation-args="{{ [1, 1] }}"
+				 validation-args="{{ [[1, 1],[1, '1']] }}"
 				 result-type="any">
 		</tb-js-func>
 	</section>	
@@ -64,7 +64,7 @@
 		<tb-js-func ng-if="model.usePostProcessing"
 		         ng-model="model.postFuncBody"
 				 function-args="{{ ['time', 'value', 'prevValue'] }}"
-				 validation-args="{{ [1, 1, 1] }}"
+				 validation-args="{{ [[1, 1, 1],[1, '1', '1']] }}"
 				 result-type="any">
 		</tb-js-func>
 	</section>
diff --git a/ui/src/app/components/js-func.directive.js b/ui/src/app/components/js-func.directive.js
index 22b9ba5..ad53471 100644
--- a/ui/src/app/components/js-func.directive.js
+++ b/ui/src/app/components/js-func.directive.js
@@ -128,7 +128,20 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) {
         scope.validate = function () {
             try {
                 var toValidate = new Function(scope.functionArgsString, scope.functionBody);
-                var res = toValidate.apply(this, scope.validationArgs);
+                var res;
+                var validationError;
+                for (var i=0;i<scope.validationArgs.length;i++) {
+                    try {
+                        res = toValidate.apply(this, scope.validationArgs[i]);
+                        validationError = null;
+                        break;
+                    } catch (e) {
+                        validationError = e;
+                    }
+                }
+                if (validationError) {
+                    throw validationError;
+                }
                 if (scope.resultType != 'nocheck') {
                     if (scope.resultType === 'any') {
                         if (angular.isUndefined(res)) {
diff --git a/ui/src/app/components/react/json-form-css.jsx b/ui/src/app/components/react/json-form-css.jsx
new file mode 100644
index 0000000..9c5dcc6
--- /dev/null
+++ b/ui/src/app/components/react/json-form-css.jsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016-2017 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.
+ */
+import React from 'react';
+import ThingsboardAceEditor from './json-form-ace-editor.jsx';
+import 'brace/mode/css';
+import beautify from 'js-beautify';
+
+const css_beautify = beautify.css;
+
+class ThingsboardCss extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onTidyCss = this.onTidyCss.bind(this);
+    }
+
+    onTidyCss(css) {
+        return css_beautify(css, {indent_size: 4});
+    }
+
+    render() {
+        return (
+            <ThingsboardAceEditor {...this.props} mode='css' onTidy={this.onTidyCss} {...this.state}></ThingsboardAceEditor>
+        );
+    }
+}
+
+export default ThingsboardCss;
diff --git a/ui/src/app/components/react/json-form-html.jsx b/ui/src/app/components/react/json-form-html.jsx
new file mode 100644
index 0000000..f45952f
--- /dev/null
+++ b/ui/src/app/components/react/json-form-html.jsx
@@ -0,0 +1,41 @@
+/*
+ * Copyright © 2016-2017 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.
+ */
+import React from 'react';
+import ThingsboardAceEditor from './json-form-ace-editor.jsx';
+import 'brace/mode/html';
+import beautify from 'js-beautify';
+
+const html_beautify = beautify.html;
+
+class ThingsboardHtml extends React.Component {
+
+    constructor(props) {
+        super(props);
+        this.onTidyHtml = this.onTidyHtml.bind(this);
+    }
+
+    onTidyHtml(html) {
+        return html_beautify(html, {indent_size: 4});
+    }
+
+    render() {
+        return (
+            <ThingsboardAceEditor {...this.props} mode='html' onTidy={this.onTidyHtml} {...this.state}></ThingsboardAceEditor>
+        );
+    }
+}
+
+export default ThingsboardHtml;
diff --git a/ui/src/app/components/react/json-form-schema-form.jsx b/ui/src/app/components/react/json-form-schema-form.jsx
index 734ae12..d708d53 100644
--- a/ui/src/app/components/react/json-form-schema-form.jsx
+++ b/ui/src/app/components/react/json-form-schema-form.jsx
@@ -19,6 +19,8 @@ import { utils } from 'react-schema-form';
 import ThingsboardArray from './json-form-array.jsx';
 import ThingsboardJavaScript from './json-form-javascript.jsx';
 import ThingsboardJson from './json-form-json.jsx';
+import ThingsboardHtml from './json-form-html.jsx';
+import ThingsboardCss from './json-form-css.jsx';
 import ThingsboardColor from './json-form-color.jsx'
 import ThingsboardRcSelect from './json-form-rc-select.jsx';
 import ThingsboardNumber from './json-form-number.jsx';
@@ -52,6 +54,8 @@ class ThingsboardSchemaForm extends React.Component {
             'array': ThingsboardArray,
             'javascript': ThingsboardJavaScript,
             'json': ThingsboardJson,
+            'html': ThingsboardHtml,
+            'css': ThingsboardCss,
             'color': ThingsboardColor,
             'rc-select': ThingsboardRcSelect,
             'fieldset': ThingsboardFieldSet
diff --git a/ui/src/app/components/widget-config.directive.js b/ui/src/app/components/widget-config.directive.js
index da12a49..775000a 100644
--- a/ui/src/app/components/widget-config.directive.js
+++ b/ui/src/app/components/widget-config.directive.js
@@ -74,11 +74,12 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
                 scope.selectedTab = 0;
                 scope.title = ngModelCtrl.$viewValue.title;
                 scope.showTitle = ngModelCtrl.$viewValue.showTitle;
+                scope.dropShadow = angular.isDefined(ngModelCtrl.$viewValue.dropShadow) ? ngModelCtrl.$viewValue.dropShadow : true;
                 scope.backgroundColor = ngModelCtrl.$viewValue.backgroundColor;
                 scope.color = ngModelCtrl.$viewValue.color;
                 scope.padding = ngModelCtrl.$viewValue.padding;
                 scope.timewindow = ngModelCtrl.$viewValue.timewindow;
-                if (scope.widgetType !== types.widgetType.rpc.value) {
+                if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) {
                     if (scope.datasources) {
                         scope.datasources.splice(0, scope.datasources.length);
                     } else {
@@ -89,7 +90,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
                             scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
                         }
                     }
-                } else {
+                } else if (scope.widgetType === types.widgetType.rpc.value) {
                     if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
                         var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
                         if (scope.deviceAliases[aliasId]) {
@@ -140,18 +141,19 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
                 if (scope.widgetType === types.widgetType.rpc.value) {
                     valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
                     ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
-                } else {
+                } else if (scope.widgetType !== types.widgetType.static.value) {
                     valid = value && value.datasources && value.datasources.length > 0;
                     ngModelCtrl.$setValidity('datasources', valid);
                 }
             }
         };
 
-        scope.$watch('title + showTitle + backgroundColor + color + padding + intervalSec', function () {
+        scope.$watch('title + showTitle + dropShadow + backgroundColor + color + padding + intervalSec', function () {
             if (ngModelCtrl.$viewValue) {
                 var value = ngModelCtrl.$viewValue;
                 value.title = scope.title;
                 value.showTitle = scope.showTitle;
+                value.dropShadow = scope.dropShadow;
                 value.backgroundColor = scope.backgroundColor;
                 value.color = scope.color;
                 value.padding = scope.padding;
@@ -177,7 +179,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
         }, true);
 
         scope.$watch('datasources', function () {
-            if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value) {
+            if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value
+                && scope.widgetType !== types.widgetType.static.value) {
                 var value = ngModelCtrl.$viewValue;
                 if (value.datasources) {
                     value.datasources.splice(0, value.datasources.length);
@@ -235,7 +238,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, types, utils) {
         };
 
         scope.updateDatasourcesAccordionState = function () {
-            if (scope.widgetType !== types.widgetType.rpc.value) {
+            if (scope.widgetType !== types.widgetType.rpc.value &&
+                scope.widgetType !== types.widgetType.static.value) {
                 if (scope.datasourcesAccordion) {
                     scope.updateDatasourcesAccordionStatePending = false;
                     var expand = scope.datasources && scope.datasources.length < 4;
diff --git a/ui/src/app/components/widget-config.tpl.html b/ui/src/app/components/widget-config.tpl.html
index d988677..87b3afd 100644
--- a/ui/src/app/components/widget-config.tpl.html
+++ b/ui/src/app/components/widget-config.tpl.html
@@ -31,6 +31,11 @@
                                      ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
                         </md-checkbox>
                     </div>
+                    <div layout="row" layout-padding>
+                        <md-checkbox flex aria-label="{{ 'widget-config.drop-shadow' | translate }}"
+                                     ng-model="dropShadow">{{ 'widget-config.drop-shadow' | translate }}
+                        </md-checkbox>
+                    </div>
                     <div flex
                          md-color-picker
                          ng-model="backgroundColor"
@@ -64,7 +69,7 @@
                     <tb-timewindow as-button="true" flex ng-model="timewindow"></tb-timewindow>
                 </div>
                 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
-                             ng-show="widgetType !== types.widgetType.rpc.value">
+                             ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value">
                     <v-pane id="datasources-pane" expanded="forceExpandDatasources">
                         <v-pane-header>
                             {{ 'widget-config.datasources' | translate }}
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index 02b4e31..2599085 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -66,6 +66,10 @@ export default function AddWidgetController($scope, widgetService, deviceService
                     link = 'widgetsConfigRpc';
                     break;
                 }
+                case types.widgetType.static.value: {
+                    link = 'widgetsConfigStatic';
+                    break;
+                }
             }
         }
         return link;
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 018bda4..49bd3bd 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -41,7 +41,9 @@ export default function DashboardController(types, widgetService, userService,
     vm.latestWidgetTypes = [];
     vm.timeseriesWidgetTypes = [];
     vm.rpcWidgetTypes = [];
+    vm.staticWidgetTypes = [];
     vm.widgetEditMode = $state.$current.data.widgetEditMode;
+    vm.iframeMode = $rootScope.iframeMode;
     vm.widgets = [];
 
     vm.addWidget = addWidget;
@@ -82,6 +84,7 @@ export default function DashboardController(types, widgetService, userService,
         vm.latestWidgetTypes = [];
         vm.timeseriesWidgetTypes = [];
         vm.rpcWidgetTypes = [];
+        vm.staticWidgetTypes = [];
         if (vm.widgetsBundle) {
             var bundleAlias = vm.widgetsBundle.alias;
             var isSystem = vm.widgetsBundle.tenantId.id === types.id.nullUid;
@@ -127,6 +130,8 @@ export default function DashboardController(types, widgetService, userService,
                             vm.latestWidgetTypes.push(widget);
                         } else if (widgetTypeInfo.type === types.widgetType.rpc.value) {
                             vm.rpcWidgetTypes.push(widget);
+                        } else if (widgetTypeInfo.type === types.widgetType.static.value) {
+                            vm.staticWidgetTypes.push(widget);
                         }
                         top += sizeY;
                         loadNextOrComplete(i);
@@ -442,6 +447,10 @@ export default function DashboardController(types, widgetService, userService,
                     link = 'widgetsConfigRpc';
                     break;
                 }
+                case types.widgetType.static.value: {
+                    link = 'widgetsConfigStatic';
+                    break;
+                }
             }
         }
         return link;
@@ -490,6 +499,7 @@ export default function DashboardController(types, widgetService, userService,
         vm.timeseriesWidgetTypes = [];
         vm.latestWidgetTypes = [];
         vm.rpcWidgetTypes = [];
+        vm.staticWidgetTypes = [];
     }
 
     function addWidgetFromType(event, widget) {
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 109c10a..0b2bdd6 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -15,7 +15,7 @@
     limitations under the License.
 
 -->
-<md-content flex tb-expand-fullscreen="vm.widgetEditMode" hide-expand-button="vm.widgetEditMode">
+<md-content flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode" hide-expand-button="vm.widgetEditMode || vm.iframeMode">
     <!--section ng-show="!vm.isAddingWidget && !loading && !vm.widgetEditMode" layout="row" layout-wrap
              class="tb-header-buttons tb-top-header-buttons md-fab" ng-style="{'right': '50px'}">
         <md-button ng-if="vm.isTenantAdmin()" ng-show="vm.isEdit" ng-disabled="loading"
@@ -136,7 +136,7 @@
         </header-pane>
         <div>
             <md-tabs ng-if="vm.timeseriesWidgetTypes.length > 0 || vm.latestWidgetTypes.length > 0 ||
-                            vm.rpcWidgetTypes.length > 0"
+                            vm.rpcWidgetTypes.length > 0 || vm.staticWidgetTypes.length > 0"
                      flex
                      class="tb-absolute-fill" md-border-bottom>
                 <md-tab ng-if="vm.timeseriesWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.timeseries' | translate }}">
@@ -169,9 +169,19 @@
                             on-widget-clicked="vm.addWidgetFromType(event, widget)">
                     </tb-dashboard>
                 </md-tab>
+                <md-tab ng-if="vm.staticWidgetTypes.length > 0" style="height: 100%;" label="{{ 'widget.static' | translate }}">
+                    <tb-dashboard
+                            widgets="vm.staticWidgetTypes"
+                            is-edit="false"
+                            is-mobile="true"
+                            is-edit-action-enabled="false"
+                            is-remove-action-enabled="false"
+                            on-widget-clicked="vm.addWidgetFromType(event, widget)">
+                    </tb-dashboard>
+                </md-tab>
             </md-tabs>
             <span translate ng-if="vm.timeseriesWidgetTypes.length === 0 && vm.latestWidgetTypes.length === 0 &&
-                                   vm.rpcWidgetTypes.length === 0 && vm.widgetsBundle"
+                                   vm.rpcWidgetTypes.length === 0 && vm.staticWidgetTypes.length === 0 && vm.widgetsBundle"
                   layout-align="center center"
                   style="text-transform: uppercase; display: flex;"
                   class="md-headline tb-absolute-fill">widgets-bundle.empty</span>
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
index 63794dc..bec99ea 100644
--- a/ui/src/app/device/attribute/attribute-table.directive.js
+++ b/ui/src/app/device/attribute/attribute-table.directive.js
@@ -305,31 +305,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
                                 for (var i = 0; i < widgetTypes.length; i++) {
                                     var widgetType = widgetTypes[i];
                                     var widgetInfo = widgetService.toWidgetInfo(widgetType);
-                                    var sizeX = widgetInfo.sizeX*2;
-                                    var sizeY = widgetInfo.sizeY*2;
-                                    var col = Math.floor(Math.max(0, (20 - sizeX)/2));
-                                    var widget = {
-                                        isSystemType: isSystem,
-                                        bundleAlias: bundleAlias,
-                                        typeAlias: widgetInfo.alias,
-                                        type: widgetInfo.type,
-                                        title: widgetInfo.widgetName,
-                                        sizeX: sizeX,
-                                        sizeY: sizeY,
-                                        row: 0,
-                                        col: col,
-                                        config: angular.fromJson(widgetInfo.defaultConfig)
-                                    };
-
-                                    widget.config.title = widgetInfo.widgetName;
-                                    widget.config.datasources = [datasource];
-                                    var length;
-                                    if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
-                                        length = scope.widgetsListCache.push([widget]);
-                                        scope.widgetsList.push(length === 1 ? [widget] : []);
-                                    } else if (widgetInfo.type === types.widgetType.latest.value) {
-                                        length = scope.widgetsListCache.push([widget]);
-                                        scope.widgetsList.push(length === 1 ? [widget] : []);
+                                    if (widgetInfo.type !== types.widgetType.static.value) {
+                                        var sizeX = widgetInfo.sizeX * 2;
+                                        var sizeY = widgetInfo.sizeY * 2;
+                                        var col = Math.floor(Math.max(0, (20 - sizeX) / 2));
+                                        var widget = {
+                                            isSystemType: isSystem,
+                                            bundleAlias: bundleAlias,
+                                            typeAlias: widgetInfo.alias,
+                                            type: widgetInfo.type,
+                                            title: widgetInfo.widgetName,
+                                            sizeX: sizeX,
+                                            sizeY: sizeY,
+                                            row: 0,
+                                            col: col,
+                                            config: angular.fromJson(widgetInfo.defaultConfig)
+                                        };
+
+                                        widget.config.title = widgetInfo.widgetName;
+                                        widget.config.datasources = [datasource];
+                                        var length;
+                                        if (scope.attributeScope === types.latestTelemetry && widgetInfo.type !== types.widgetType.rpc.value) {
+                                            length = scope.widgetsListCache.push([widget]);
+                                            scope.widgetsList.push(length === 1 ? [widget] : []);
+                                        } else if (widgetInfo.type === types.widgetType.latest.value) {
+                                            length = scope.widgetsListCache.push([widget]);
+                                            scope.widgetsList.push(length === 1 ? [widget] : []);
+                                        }
                                     }
                                 }
                                 scope.widgetsLoaded = true;
diff --git a/ui/src/app/help/help-links.constant.js b/ui/src/app/help/help-links.constant.js
index 0ce0395..62d6c2d 100644
--- a/ui/src/app/help/help-links.constant.js
+++ b/ui/src/app/help/help-links.constant.js
@@ -45,7 +45,7 @@ var pluginActionsClazzHelpLinkMap = {
     'org.thingsboard.server.extensions.rest.action.RestApiCallPluginAction': 'pluginActionRestApiCall'
 };
 
-var helpBaseUrl = "http://thingsboard.io";
+var helpBaseUrl = "https://thingsboard.io";
 
 export default angular.module('thingsboard.help', [])
     .constant('helpLinks',
@@ -86,6 +86,7 @@ export default angular.module('thingsboard.help', [])
                 widgetsConfigTimeseries:  helpBaseUrl + "/docs/user-guide/ui/dashboards#timeseries",
                 widgetsConfigLatest: helpBaseUrl +  "/docs/user-guide/ui/dashboards#latest",
                 widgetsConfigRpc: helpBaseUrl +  "/docs/user-guide/ui/dashboards#rpc",
+                widgetsConfigStatic: helpBaseUrl +  "/docs/user-guide/ui/dashboards#static",
             },
             getPluginLink: function(plugin) {
                 var link = 'plugins';
diff --git a/ui/src/app/widget/select-widget-type.tpl.html b/ui/src/app/widget/select-widget-type.tpl.html
index e7ada8b..1b39835 100644
--- a/ui/src/app/widget/select-widget-type.tpl.html
+++ b/ui/src/app/widget/select-widget-type.tpl.html
@@ -53,6 +53,13 @@
                             </md-icon>
                             <span translate>{{vm.types.widgetType.rpc.name}}</span>
                         </md-button>
+                        <md-button class="tb-card-button md-raised md-primary" layout="column"
+                                   ng-click="vm.typeSelected(vm.types.widgetType.static.value)">
+                            <md-icon class="material-icons tb-md-96"
+                                     aria-label="{{ vm.types.widgetType.static.name | translate }}">font_download
+                            </md-icon>
+                            <span translate>{{vm.types.widgetType.static.name}}</span>
+                        </md-button>
                     </div>
                 </fieldset>
             </div>
diff --git a/ui/src/locale/en_US.json b/ui/src/locale/en_US.json
index cf1a97b..d9a761c 100644
--- a/ui/src/locale/en_US.json
+++ b/ui/src/locale/en_US.json
@@ -603,6 +603,7 @@
     "timeseries": "Time series",
     "latest-values": "Latest values",
     "rpc": "Control widget",
+    "static": "Static widget",
     "select-widget-type": "Select widget type",
     "missing-widget-title-error": "Widget title must be specified!",
     "widget-saved": "Widget saved",
@@ -663,6 +664,7 @@
     "title": "Title",
     "general-settings": "General settings",
     "display-title": "Display title",
+    "drop-shadow": "Drop shadow",
     "background-color": "Background color",
     "text-color": "Text color",
     "padding": "Padding",