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 1010013689..140004c37c 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -25,22 +25,6 @@ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" } }, - { - "alias": "basic_timeseries", - "name": "Timeseries - Flot", - "descriptor": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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}" - } - }, { "alias": "doughnut_chart_js", "name": "Doughnut - Chart.js", @@ -71,7 +55,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.pie-label {\n font-size: 12px;\n font-family: 'Roboto';\n font-weight: bold;\n text-align: center;\n padding: 2px;\n color: white;\n}\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \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.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \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.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n", "settingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"showPercentages\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" @@ -138,8 +122,8 @@ } }, { - "alias": "timeseries_bars_flot", - "name": "Timeseries Bars - Flot", + "alias": "state_chart", + "name": "State Chart", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -147,15 +131,15 @@ "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('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\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.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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},\"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\":{}}" + "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\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" } }, { - "alias": "state_chart", - "name": "State Chart", + "alias": "basic_timeseries", + "name": "Timeseries - Flot", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -163,11 +147,27 @@ "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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" + "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}" + } + }, + { + "alias": "timeseries_bars_flot", + "name": "Timeseries Bars - Flot", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "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.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\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},\"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\":{}}" } } ] -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/navigation_widgets.json b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json new file mode 100644 index 0000000000..c37e21906c --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json @@ -0,0 +1,41 @@ +{ + "widgetsBundle": { + "alias": "navigation_widgets", + "title": "Navigation widgets", + "image": null + }, + "widgetTypes": [ + { + "alias": "navigation_cards", + "name": "Navigation cards", + "descriptor": { + "type": "static", + "sizeX": 7, + "sizeY": 6, + "resources": [], + "templateHtml": "", + "templateCss": "/*#widget-container {\n overflow-y: auto;\n box-sizing: content-box !important;\n cursor: auto;\n}*/\n\n#widget-container #container {\n overflow-y: auto;\n box-sizing: content-box;\n cursor: auto;\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onResize = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"filterType\": {\n \"title\": \"Filter type\",\n \"type\": \"string\",\n \"default\": \"all\"\n },\n \"filter\": {\n \"title\": \"Items\",\n \"type\": \"array\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"filterType\",\n \"type\": \"radios\",\n \"direction\": \"row\",\n \"titleMap\": [\n {\n \"value\": \"all\",\n \"name\": \"All items\"\n },\n {\n \"value\": \"include\",\n \"name\": \"Include items\"\n },\n {\n \"value\": \"exclude\",\n \"name\": \"Exclude items\"\n }\n ]\n },\n {\n \"key\": \"filter\",\n \"type\": \"rc-select\",\n \"condition\": \"model.filterType !== 'all'\",\n \"tags\": true,\n \"placeholder\": \"Enter urls to filter\",\n \"items\": [{\"value\": \"/devices\", \"label\": \"/devices\"}, {\"value\": \"/assets\", \"label\": \"/assets\"}, {\"value\": \"/deviceProfies\", \"label\": \"/deviceProfies\"}]\n }\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\":\"rgba(255,255,255,0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"filterType\":\"all\"},\"title\":\"Navigation cards\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" + } + }, + { + "alias": "navigation_card", + "name": "Navigation card", + "descriptor": { + "type": "static", + "sizeX": 2.5, + "sizeY": 2, + "resources": [], + "templateHtml": "", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n\n}\n\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"name\": {\n \"title\": \"Title\",\n \"type\": \"string\",\n \"default\": \"{i18n:device.devices}\"\n },\n \"icon\": {\n \"title\": \"icon\",\n \"type\": \"string\",\n \"default\": \"devices_other\"\n },\n \"path\": {\n \"title\": \"Navigation path\",\n \"type\": \"string\",\n \"default\": \"/devices\"\n }\n },\n \"required\": [\"name\", \"icon\", \"path\"]\n },\n \"form\": [\n \"name\",\n {\n \"key\": \"icon\",\n \"type\": \"icon\"\n },\n \"path\"\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\":\"rgba(255,255,255,0)\",\"color\":\"rgba(255,255,255,0.87)\",\"padding\":\"8px\",\"settings\":{\"name\":\"{i18n:device.devices}\",\"icon\":\"devices_other\",\"path\":\"/devices\"},\"title\":\"Navigation card\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" + } + } + ] +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index b6c040acdf..c780dde8a9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,7 +32,11 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HomeDashboard; +import org.thingsboard.server.common.data.HomeDashboardInfo; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; @@ -39,7 +45,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -53,6 +61,9 @@ public class DashboardController extends BaseController { public static final String DASHBOARD_ID = "dashboardId"; + private static final String HOME_DASHBOARD_ID = "homeDashboardId"; + private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; + @Value("${dashboard.max_datapoints_limit}") private long maxDatapointsLimit; @@ -472,4 +483,100 @@ public class DashboardController extends BaseController { throw handleException(e); } } + + @PreAuthorize("isAuthenticated()") + @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) + @ResponseBody + public HomeDashboard getHomeDashboard() throws ThingsboardException { + try { + SecurityUser securityUser = getCurrentUser(); + if (securityUser.isSystemAdmin()) { + return null; + } + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + JsonNode additionalInfo = user.getAdditionalInfo(); + HomeDashboard homeDashboard; + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + if (homeDashboard == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + additionalInfo = customer.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + } + if (homeDashboard == null) { + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + additionalInfo = tenant.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + } + } + return homeDashboard; + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) + @ResponseBody + public HomeDashboardInfo getTenantHomeDashboardInfo() throws ThingsboardException { + try { + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + DashboardId dashboardId = null; + boolean hideDashboardToolbar = true; + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID)) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + dashboardId = new DashboardId(toUUID(strDashboardId)); + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + } + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { + try { + if (homeDashboardInfo.getDashboardId() != null) { + checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); + } + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { + additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + } + if (homeDashboardInfo.getDashboardId() != null) { + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString()); + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar()); + } else { + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID); + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR); + } + tenant.setAdditionalInfo(additionalInfo); + tenantService.saveTenant(tenant); + } catch (Exception e) { + throw handleException(e); + } + } + + private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { + try { + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID)) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ); + boolean hideDashboardToolbar = true; + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + return new HomeDashboard(dashboard, hideDashboardToolbar); + } + } catch (Exception e) {} + return null; + } } diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index f22dcb0951..658c4a1fba 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -185,6 +185,8 @@ public class ThingsboardInstallService { case "3.2.0": log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); + case "3.2.1": + log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ..."); log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 8bec74cff9..ac502926f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -438,6 +438,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { this.deleteSystemWidgetBundle("input_widgets"); this.deleteSystemWidgetBundle("date"); this.deleteSystemWidgetBundle("entity_admin_widgets"); + this.deleteSystemWidgetBundle("navigation_widgets"); installScripts.loadSystemWidgets(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java new file mode 100644 index 0000000000..f2985e3371 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2021 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.common.data; + +import lombok.Data; + +@Data +public class HomeDashboard extends Dashboard { + + private boolean hideDashboardToolbar; + + public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) { + super(dashboard); + this.hideDashboardToolbar = hideDashboardToolbar; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java new file mode 100644 index 0000000000..1cbc1c737e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 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.common.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.id.DashboardId; + +@Data +@AllArgsConstructor +public class HomeDashboardInfo { + private DashboardId dashboardId; + private boolean hideDashboardToolbar; +} diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 8b2c8f0078..6ca208c506 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -53,8 +53,8 @@ export class AliasController implements IAliasController { private stateControllerHolder: StateControllerHolder, private origEntityAliases: EntityAliases, private origFilters: Filters) { - this.entityAliases = deepClone(this.origEntityAliases); - this.filters = deepClone(this.origFilters); + this.entityAliases = deepClone(this.origEntityAliases) || {}; + this.filters = deepClone(this.origFilters) || {}; this.userFilters = {}; } diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index 06a76e62e1..fdba07b94a 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -15,7 +15,7 @@ /// import { Injectable, NgZone } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router'; import { AuthService } from '../auth/auth.service'; import { select, Store } from '@ngrx/store'; import { AppState } from '../core.state'; @@ -28,6 +28,7 @@ import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; +import { isObject } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -35,6 +36,7 @@ import { UtilsService } from '@core/services/utils.service'; export class AuthGuard implements CanActivate, CanActivateChild { constructor(private store: Store, + private router: Router, private authService: AuthService, private dialogService: DialogService, private utils: UtilsService, @@ -115,6 +117,14 @@ export class AuthGuard implements CanActivate, CanActivateChild { if (data.auth && data.auth.indexOf(authority) === -1) { this.dialogService.forbidden(); return of(false); + } else if (data.redirectTo) { + let redirect; + if (isObject(data.redirectTo)) { + redirect = data.redirectTo[authority]; + } else { + redirect = data.redirectTo; + } + return of(this.router.parseUrl(redirect)); } else { return of(true); } diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index a1307bb080..3f3e512752 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; -import { Dashboard, DashboardInfo } from '@shared/models/dashboard.models'; +import { Dashboard, DashboardInfo, HomeDashboard, HomeDashboardInfo } from '@shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; import { NavigationEnd, Router } from '@angular/router'; import { filter, map, publishReplay, refCount } from 'rxjs/operators'; @@ -122,6 +122,19 @@ export class DashboardService { defaultHttpOptionsFromConfig(config)); } + public getHomeDashboard(config?: RequestConfig): Observable { + return this.http.get('/api/dashboard/home', defaultHttpOptionsFromConfig(config)); + } + + public getTenantHomeDashboardInfo(config?: RequestConfig): Observable { + return this.http.get('/api/tenant/dashboard/home/info', defaultHttpOptionsFromConfig(config)); + } + + public setTenantHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo, config?: RequestConfig): Observable { + return this.http.post('/api/tenant/dashboard/home/info', homeDashboardInfo, + defaultHttpOptionsFromConfig(config)); + } + public getPublicDashboardLink(dashboard: DashboardInfo): string | null { if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { const publicCustomers = dashboard.assignedCustomers diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 98b3b9de1e..735fd6bead 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -282,6 +282,13 @@ export class MenuService { path: '/dashboards', icon: 'dashboards' }, + { + id: guid(), + name: 'admin.home-settings', + type: 'link', + path: '/settings/home', + icon: 'settings_applications' + }, { id: guid(), name: 'audit-log.audit-logs', diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html index 43f9ca4a3f..a095dae071 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html @@ -23,6 +23,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -34,6 +35,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -45,6 +47,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -56,6 +59,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -67,6 +71,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html index 7f1097444f..061a4d5227 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html @@ -53,6 +53,7 @@ [mobileRowHeight]="layoutCtx.gridSettings.mobileRowHeight" [isMobile]="isMobile" [isMobileDisabled]="widgetEditMode" + [disableWidgetInteraction]="isEdit" [isEditActionEnabled]="isEdit" [isExportActionEnabled]="isEdit && !widgetEditMode" [isRemoveActionEnabled]="isEdit && !widgetEditMode" diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index b472ca54cc..bd675a0a9f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -150,7 +150,7 @@ -
+
+ + {{settings.icon}} + {{translatedName}} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss new file mode 100644 index 0000000000..18a81aa216 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 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. + */ + +:host { + width: 100%; + height: 100%; +} + +:host ::ng-deep { + .tb-nav-button { + width: 100%; + height: 100%; + &:hover { + border-bottom: none; + } + &:focus { + border-bottom: none; + } + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 36px; + max-height: 36px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts new file mode 100644 index 0000000000..5dbd8022ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts @@ -0,0 +1,66 @@ +/// +/// Copyright © 2016-2021 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 { PageComponent } from '@shared/components/page.component'; +import { Component, Input, NgZone, OnInit } from '@angular/core'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { UtilsService } from '@core/services/utils.service'; + +interface NavigationCardWidgetSettings { + name: string; + icon: string; + path: string; +} + +@Component({ + selector: 'tb-navigation-card-widget', + templateUrl: './navigation-card-widget.component.html', + styleUrls: ['./navigation-card-widget.component.scss'] +}) +export class NavigationCardWidgetComponent extends PageComponent implements OnInit { + + settings: NavigationCardWidgetSettings; + + translatedName: string; + + @Input() + ctx: WidgetContext; + + constructor(protected store: Store, + private utils: UtilsService, + private ngZone: NgZone, + private router: Router) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.navigationCardWidget = this; + this.settings = this.ctx.settings; + this.translatedName = this.utils.customTranslation(this.settings.name, this.settings.name); + } + + + navigate($event: Event, path: string) { + $event.preventDefault(); + this.ngZone.run(() => { + this.router.navigateByUrl(path); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html new file mode 100644 index 0000000000..a9ed8e76bb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html @@ -0,0 +1,37 @@ + + + + + + {{section.name}} + + + + + + {{place.icon}} + + {{place.name}} + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss new file mode 100644 index 0000000000..6a0e60c20b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2021 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 '../../../../../../scss/constants'; + +:host { + width: 100%; + height: 100%; +} + +:host ::ng-deep { + .tb-navigation-cards { + .mat-headline { + font-size: 20px; + @media #{$mat-gt-xmd} { + font-size: 24px; + } + } + mat-card { + padding: 0; + margin: 8px; + mat-card-title { + margin: 0; + padding: 24px 16px 16px; + } + mat-card-title+mat-card-content { + padding-top: 0; + } + mat-card-content { + padding: 16px; + } + } + .tb-card-button { + width: 100%; + height: 100%; + max-width: 240px; + &:hover { + border-bottom: none; + } + &:focus { + border-bottom: none; + } + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 36px; + max-height: 36px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts new file mode 100644 index 0000000000..7628c30457 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2021 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 { PageComponent } from '@shared/components/page.component'; +import { Component, Input, NgZone, OnInit } from '@angular/core'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MenuService } from '@core/services/menu.service'; +import { HomeSection, HomeSectionPlace } from '@core/services/menu.models'; +import { Router } from '@angular/router'; +import { map } from 'rxjs/operators'; + +interface NavigationCardsWidgetSettings { + filterType: 'all' | 'include' | 'exclude'; + filter: string[]; +} + +@Component({ + selector: 'tb-navigation-cards-widget', + templateUrl: './navigation-cards-widget.component.html', + styleUrls: ['./navigation-cards-widget.component.scss'] +}) +export class NavigationCardsWidgetComponent extends PageComponent implements OnInit { + + homeSections$ = this.menuService.homeSections(); + showHomeSections$ = this.homeSections$.pipe( + map((sections) => { + return sections.filter((section) => this.sectionPlaces(section).length > 0); + }) + ); + + cols = null; + + settings: NavigationCardsWidgetSettings; + + @Input() + ctx: WidgetContext; + + constructor(protected store: Store, + private menuService: MenuService, + private ngZone: NgZone, + private router: Router) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.navigationCardsWidget = this; + this.settings = this.ctx.settings; + } + + resize() { + this.updateColumnCount(); + } + + private updateColumnCount() { + this.cols = 2; + const width = this.ctx.width; + if (width >= 1280) { + this.cols = 3; + if (width >= 1920) { + this.cols = 4; + } + } + this.ctx.detectChanges(); + } + + navigate($event: Event, path: string) { + $event.preventDefault(); + this.ngZone.run(() => { + this.router.navigateByUrl(path); + }); + } + + sectionPlaces(section: HomeSection): HomeSectionPlace[] { + return section && section.places ? section.places.filter((place) => this.filterPlace(place)) : []; + } + + private filterPlace(place: HomeSectionPlace): boolean { + if (this.settings.filterType === 'include') { + return this.settings.filter.includes(place.path); + } else if (this.settings.filterType === 'exclude') { + return !this.settings.filter.includes(place.path); + } + return true; + } + + sectionColspan(section: HomeSection): number { + if (this.ctx.width >= 960) { + let colspan = this.cols; + const places = this.sectionPlaces(section); + if (places.length <= colspan) { + colspan = places.length; + } + return colspan; + } else { + return 2; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index 0c143f2b57..2d080bc8e9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -35,6 +35,8 @@ import { TripAnimationComponent } from './trip-animation/trip-animation.componen import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; import { ImportExportService } from '@home/components/import-export/import-export.service'; +import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navigation-cards-widget.component'; +import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component'; @NgModule({ declarations: @@ -50,7 +52,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor MultipleInputWidgetComponent, TripAnimationComponent, PhotoCameraInputWidgetComponent, - GatewayFormComponent + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent ], imports: [ CommonModule, @@ -68,7 +72,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor MultipleInputWidgetComponent, TripAnimationComponent, PhotoCameraInputWidgetComponent, - GatewayFormComponent + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent ], providers: [ CustomDialogService, diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 1df4aed59e..1928a1f404 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -32,6 +32,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { OAuth2Service } from '@core/http/oauth2.service'; import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -48,7 +49,7 @@ const routes: Routes = [ { path: 'settings', data: { - auth: [Authority.SYS_ADMIN], + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], breadcrumb: { label: 'admin.system-settings', icon: 'settings' @@ -57,8 +58,13 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: 'general', - pathMatch: 'full' + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + redirectTo: { + SYS_ADMIN: '/settings/general', + TENANT_ADMIN: '/settings/home' + } + } }, { path: 'general', @@ -127,6 +133,19 @@ const routes: Routes = [ resolve: { loginProcessingUrl: OAuth2LoginProcessingUrlResolver } + }, + { + path: 'home', + component: HomeSettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.TENANT_ADMIN], + title: 'admin.home-settings', + breadcrumb: { + label: 'admin.home-settings', + icon: 'settings_applications' + } + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index a170ce10b4..1671220cfe 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -26,6 +26,7 @@ import { HomeComponentsModule } from '@modules/home/components/home-components.m import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; @NgModule({ declarations: @@ -35,7 +36,8 @@ import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dial SmsProviderComponent, SendTestSmsDialogComponent, SecuritySettingsComponent, - OAuth2SettingsComponent + OAuth2SettingsComponent, + HomeSettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html new file mode 100644 index 0000000000..0191724a24 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html @@ -0,0 +1,54 @@ + +
+ + +
+ admin.home-settings +
+
+ + +
+ +
+
+
+
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
+
+
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss new file mode 100644 index 0000000000..482889cf77 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 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 "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts new file mode 100644 index 0000000000..a92579d701 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts @@ -0,0 +1,84 @@ +/// +/// Copyright © 2016-2021 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 { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { DashboardService } from '@core/http/dashboard.service'; +import { HomeDashboardInfo } from '@shared/models/dashboard.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { DashboardId } from '@shared/models/id/dashboard-id'; + +@Component({ + selector: 'tb-home-settings', + templateUrl: './home-settings.component.html', + styleUrls: ['./home-settings.component.scss', './settings-card.scss'] +}) +export class HomeSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { + + homeSettings: FormGroup; + + constructor(protected store: Store, + private router: Router, + private dashboardService: DashboardService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.homeSettings = this.fb.group({ + dashboardId: [null], + hideDashboardToolbar: [true] + }); + this.dashboardService.getTenantHomeDashboardInfo().subscribe( + (homeDashboardInfo) => { + this.setHomeDashboardInfo(homeDashboardInfo); + } + ); + } + + save(): void { + const strDashboardId = this.homeSettings.get('dashboardId').value; + const dashboardId: DashboardId = strDashboardId ? new DashboardId(strDashboardId) : null; + const hideDashboardToolbar = this.homeSettings.get('hideDashboardToolbar').value; + const homeDashboardInfo: HomeDashboardInfo = { + dashboardId, + hideDashboardToolbar + }; + this.dashboardService.setTenantHomeDashboardInfo(homeDashboardInfo).subscribe( + () => { + this.setHomeDashboardInfo(homeDashboardInfo); + } + ); + } + + confirmForm(): FormGroup { + return this.homeSettings; + } + + private setHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo) { + this.homeSettings.reset({ + dashboardId: homeDashboardInfo?.dashboardId?.id, + hideDashboardToolbar: isDefinedAndNotNull(homeDashboardInfo?.hideDashboardToolbar) ? + homeDashboardInfo?.hideDashboardToolbar : true + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.html b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html index fce5c1926c..392ae7b6c4 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.html +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html @@ -72,6 +72,21 @@ customer.description +
+
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss b/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss new file mode 100644 index 0000000000..3a4ec550cd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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 "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts index 809beef5cd..8c609fe2ef 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts @@ -23,10 +23,12 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-customer', - templateUrl: './customer.component.html' + templateUrl: './customer.component.html', + styleUrls: ['./customer.component.scss'] }) export class CustomerComponent extends ContactBasedComponent { @@ -54,7 +56,10 @@ export class CustomerComponent extends ContactBasedComponent { title: [entity ? entity.title : '', [Validators.required]], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -65,6 +70,11 @@ export class CustomerComponent extends ContactBasedComponent { this.isPublic = entity.additionalInfo && entity.additionalInfo.isPublic; this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } onCustomerIdCopied(event) { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts index c1aa7764f0..003ba5d34a 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -14,11 +14,25 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; import { HomeLinksComponent } from './home-links.component'; import { Authority } from '@shared/models/authority.enum'; +import { Observable } from 'rxjs'; +import { HomeDashboard } from '@shared/models/dashboard.models'; +import { DashboardService } from '@core/http/dashboard.service'; + +@Injectable() +export class HomeDashboardResolver implements Resolve { + + constructor(private dashboardService: DashboardService) { + } + + resolve(): Observable { + return this.dashboardService.getHomeDashboard(); + } +} const routes: Routes = [ { @@ -31,12 +45,18 @@ const routes: Routes = [ label: 'home.home', icon: 'home' } + }, + resolve: { + homeDashboard: HomeDashboardResolver } } ]; @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], + providers: [ + HomeDashboardResolver + ] }) export class HomeLinksRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html index 85b5b388a7..f519d22637 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html @@ -15,23 +15,26 @@ limitations under the License. --> - - - - - {{section.name}} - - - - - - {{place.icon}} - - {{place.name}} - - - - - - - + + + + + + + {{section.name}} + + + + + + {{place.icon}} + + {{place.name}} + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss index a97e7083e0..20949642ab 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss @@ -15,6 +15,11 @@ */ @import '../../../../../scss/constants'; +:host { + width: 100%; + height: 100%; +} + :host ::ng-deep { .tb-home-links { .mat-headline { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts index 7d5348c04f..7f485df45c 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts @@ -19,6 +19,8 @@ import { MenuService } from '@core/services/menu.service'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { HomeSection } from '@core/services/menu.models'; +import { ActivatedRoute } from '@angular/router'; +import { HomeDashboard } from '@shared/models/dashboard.models'; @Component({ selector: 'tb-home-links', @@ -31,15 +33,20 @@ export class HomeLinksComponent implements OnInit { cols = 2; + homeDashboard: HomeDashboard = this.route.snapshot.data.homeDashboard; + constructor(private menuService: MenuService, - public breakpointObserver: BreakpointObserver) { + public breakpointObserver: BreakpointObserver, + private route: ActivatedRoute) { } ngOnInit() { - this.updateColumnCount(); - this.breakpointObserver - .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) - .subscribe((state: BreakpointState) => this.updateColumnCount()); + if (!this.homeDashboard) { + this.updateColumnCount(); + this.breakpointObserver + .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) + .subscribe((state: BreakpointState) => this.updateColumnCount()); + } } private updateColumnCount() { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts index e64b9b1768..644645c1a9 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts @@ -20,6 +20,7 @@ import { CommonModule } from '@angular/common'; import { HomeLinksRoutingModule } from './home-links-routing.module'; import { HomeLinksComponent } from './home-links.component'; import { SharedModule } from '@app/shared/shared.module'; +import { HomeComponentsModule } from '@home/components/home-components.module'; @NgModule({ declarations: @@ -29,6 +30,7 @@ import { SharedModule } from '@app/shared/shared.module'; imports: [ CommonModule, SharedModule, + HomeComponentsModule, HomeLinksRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 7ab52a373a..eaca7133a0 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -63,6 +63,20 @@ +
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss new file mode 100644 index 0000000000..3a4ec550cd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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 "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index a24a17e6bf..30c0dc36aa 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -23,11 +23,12 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-tenant', templateUrl: './tenant.component.html', - styleUrls: [] + styleUrls: ['./tenant.component.scss'] }) export class TenantComponent extends ContactBasedComponent { @@ -54,7 +55,10 @@ export class TenantComponent extends ContactBasedComponent { tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -65,6 +69,11 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } updateFormState() { diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index fbc5f3c1c7..20821031b0 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -95,6 +95,20 @@ {{ 'user.always-fullscreen' | translate }} +
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index 585723b659..b9326a696f 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -23,7 +23,7 @@ import { User } from '@shared/models/user.model'; import { selectAuth } from '@core/auth/auth.selectors'; import { map } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; -import { isUndefined } from '@core/utils'; +import { isDefinedAndNotNull, isUndefined } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @Component({ @@ -74,6 +74,9 @@ export class UserComponent extends EntityComponent { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], defaultDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null], defaultDashboardFullscreen: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -89,6 +92,11 @@ export class UserComponent extends EntityComponent { {defaultDashboardId: entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null}}); this.entityForm.patchValue({additionalInfo: {defaultDashboardFullscreen: entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html index 802bb8e65a..e48eba1ebf 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html @@ -35,6 +35,7 @@ [isEditActionEnabled]="true" [isExportActionEnabled]="true" [isRemoveActionEnabled]="!isReadOnly" + [disableWidgetInteraction]="true" [callbacks]="dashboardCallbacks"> diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx index 876d93c6ff..c22779897e 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx @@ -28,12 +28,17 @@ class ThingsboardRadios extends React.Component {this.props.form.title} - { + { this.props.onChangeValidate(e); }}> {items} diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx index 571f36eab7..dcfe453b61 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx @@ -22,6 +22,7 @@ import { KeyLabelItem } from '@shared/components/json-form/react/json-form.models'; import { Mode } from 'rc-select/lib/interface'; +import { deepClone } from '@core/utils'; interface ThingsboardRcSelectState extends JsonFormFieldState { currentValue: KeyLabelItem | KeyLabelItem[]; @@ -151,10 +152,14 @@ class ThingsboardRcSelect extends React.Component {options} diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 1637890ce5..a92bc3d672 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -106,6 +106,15 @@ export interface Dashboard extends DashboardInfo { configuration?: DashboardConfiguration; } +export interface HomeDashboard extends Dashboard { + hideDashboardToolbar: boolean; +} + +export interface HomeDashboardInfo { + dashboardId: DashboardId; + hideDashboardToolbar: boolean; +} + export function isPublicDashboard(dashboard: DashboardInfo): boolean { if (dashboard && dashboard.assignedCustomers) { return dashboard.assignedCustomers diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ea0e45cbd2..32c67e2fe0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -74,6 +74,7 @@ "admin": { "general": "General", "general-settings": "General Settings", + "home-settings": "Home Settings", "outgoing-mail": "Mail Server", "outgoing-mail-settings": "Outgoing Mail Server Settings", "system-settings": "System Settings", @@ -764,7 +765,9 @@ "select-state": "Select target state", "state-controller": "State controller", "search": "Search dashboards", - "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected" + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected", + "home-dashboard": "Home dashboard", + "home-dashboard-hide-toolbar": "Hide home dashboard toolbar" }, "datakey": { "settings": "Settings", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index dcdc0909c0..b993a8dbd4 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1118,6 +1118,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@7.10.4", "@babel/template@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -7941,7 +7948,7 @@ rc-util@^4.15.3: react-lifecycles-compat "^3.0.4" shallowequal "^1.1.0" -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== @@ -7949,6 +7956,15 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: react-is "^16.12.0" shallowequal "^1.1.0" +rc-util@^5.0.6: + version "5.7.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.7.0.tgz#776b14cf5bbfc24f419fd40c42ffadddda0718fc" + integrity sha512-0hh5XkJ+vBDeMJsHElqT1ijMx+gC3gpClwQ10h/5hccrrgrMx8VUem183KLlH1YrWCfMMPmDXWWNnwsn+p6URw== + dependencies: + "@babel/runtime" "^7.12.5" + react-is "^16.12.0" + shallowequal "^1.1.0" + rc-virtual-list@^1.1.2: version "1.1.6" resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.6.tgz#b255baf9aacde149a8893324e6307214094f4c0a"