Commit 9b4309c61a5349a7ea6c7bf45a5d69ccc34a524a
Merge remote-tracking branch 'upstream/master' into develop/3.3
Showing
102 changed files
with
3121 additions
and
435 deletions
... | ... | @@ -26,22 +26,6 @@ |
26 | 26 | } |
27 | 27 | }, |
28 | 28 | { |
29 | - "alias": "basic_timeseries", | |
30 | - "name": "Timeseries - Flot", | |
31 | - "descriptor": { | |
32 | - "type": "timeseries", | |
33 | - "sizeX": 8, | |
34 | - "sizeY": 5, | |
35 | - "resources": [], | |
36 | - "templateHtml": "", | |
37 | - "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", | |
38 | - "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", | |
39 | - "settingsSchema": "{}", | |
40 | - "dataKeySettingsSchema": "{}", | |
41 | - "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}" | |
42 | - } | |
43 | - }, | |
44 | - { | |
45 | 29 | "alias": "doughnut_chart_js", |
46 | 30 | "name": "Doughnut - Chart.js", |
47 | 31 | "descriptor": { |
... | ... | @@ -71,7 +55,7 @@ |
71 | 55 | "resources": [], |
72 | 56 | "templateHtml": "", |
73 | 57 | "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", |
74 | - "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", | |
58 | + "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", | |
75 | 59 | "settingsSchema": "{}\n", |
76 | 60 | "dataKeySettingsSchema": "{}\n", |
77 | 61 | "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 @@ |
138 | 122 | } |
139 | 123 | }, |
140 | 124 | { |
141 | - "alias": "timeseries_bars_flot", | |
142 | - "name": "Timeseries Bars - Flot", | |
125 | + "alias": "state_chart", | |
126 | + "name": "State Chart", | |
143 | 127 | "descriptor": { |
144 | 128 | "type": "timeseries", |
145 | 129 | "sizeX": 8, |
... | ... | @@ -147,15 +131,15 @@ |
147 | 131 | "resources": [], |
148 | 132 | "templateHtml": "", |
149 | 133 | "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", |
150 | - "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", | |
134 | + "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", | |
151 | 135 | "settingsSchema": "{}", |
152 | 136 | "dataKeySettingsSchema": "{}", |
153 | - "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\":{}}" | |
137 | + "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}}" | |
154 | 138 | } |
155 | 139 | }, |
156 | 140 | { |
157 | - "alias": "state_chart", | |
158 | - "name": "State Chart", | |
141 | + "alias": "basic_timeseries", | |
142 | + "name": "Timeseries - Flot", | |
159 | 143 | "descriptor": { |
160 | 144 | "type": "timeseries", |
161 | 145 | "sizeX": 8, |
... | ... | @@ -163,11 +147,27 @@ |
163 | 147 | "resources": [], |
164 | 148 | "templateHtml": "", |
165 | 149 | "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", |
166 | - "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", | |
150 | + "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", | |
167 | 151 | "settingsSchema": "{}", |
168 | 152 | "dataKeySettingsSchema": "{}", |
169 | - "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}}" | |
153 | + "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}" | |
154 | + } | |
155 | + }, | |
156 | + { | |
157 | + "alias": "timeseries_bars_flot", | |
158 | + "name": "Timeseries Bars - Flot", | |
159 | + "descriptor": { | |
160 | + "type": "timeseries", | |
161 | + "sizeX": 8, | |
162 | + "sizeY": 5, | |
163 | + "resources": [], | |
164 | + "templateHtml": "", | |
165 | + "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", | |
166 | + "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", | |
167 | + "settingsSchema": "{}", | |
168 | + "dataKeySettingsSchema": "{}", | |
169 | + "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\":{}}" | |
170 | 170 | } |
171 | 171 | } |
172 | 172 | ] |
173 | -} | |
173 | +} | |
\ No newline at end of file | ... | ... |
1 | +{ | |
2 | + "widgetsBundle": { | |
3 | + "alias": "navigation_widgets", | |
4 | + "title": "Navigation widgets", | |
5 | + "image": null | |
6 | + }, | |
7 | + "widgetTypes": [ | |
8 | + { | |
9 | + "alias": "navigation_cards", | |
10 | + "name": "Navigation cards", | |
11 | + "descriptor": { | |
12 | + "type": "static", | |
13 | + "sizeX": 7, | |
14 | + "sizeY": 6, | |
15 | + "resources": [], | |
16 | + "templateHtml": "<tb-navigation-cards-widget [ctx]=\"ctx\"></tb-navigation-cards-widget>", | |
17 | + "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}", | |
18 | + "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", | |
19 | + "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", | |
20 | + "dataKeySettingsSchema": "{}\n", | |
21 | + "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}" | |
22 | + } | |
23 | + }, | |
24 | + { | |
25 | + "alias": "navigation_card", | |
26 | + "name": "Navigation card", | |
27 | + "descriptor": { | |
28 | + "type": "static", | |
29 | + "sizeX": 2.5, | |
30 | + "sizeY": 2, | |
31 | + "resources": [], | |
32 | + "templateHtml": "<tb-navigation-card-widget [ctx]=\"ctx\"></tb-navigation-card-widget>", | |
33 | + "templateCss": "", | |
34 | + "controllerScript": "self.onInit = function() {\n\n}\n\n\nself.onDestroy = function() {\n}\n", | |
35 | + "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", | |
36 | + "dataKeySettingsSchema": "{}\n", | |
37 | + "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}" | |
38 | + } | |
39 | + } | |
40 | + ] | |
41 | +} | |
\ No newline at end of file | ... | ... |
... | ... | @@ -15,6 +15,8 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.controller; |
17 | 17 | |
18 | +import com.fasterxml.jackson.databind.JsonNode; | |
19 | +import com.fasterxml.jackson.databind.node.ObjectNode; | |
18 | 20 | import org.springframework.beans.factory.annotation.Value; |
19 | 21 | import org.springframework.http.HttpStatus; |
20 | 22 | import org.springframework.security.access.prepost.PreAuthorize; |
... | ... | @@ -30,7 +32,11 @@ import org.thingsboard.server.common.data.Customer; |
30 | 32 | import org.thingsboard.server.common.data.Dashboard; |
31 | 33 | import org.thingsboard.server.common.data.DashboardInfo; |
32 | 34 | import org.thingsboard.server.common.data.EntityType; |
35 | +import org.thingsboard.server.common.data.HomeDashboard; | |
36 | +import org.thingsboard.server.common.data.HomeDashboardInfo; | |
33 | 37 | import org.thingsboard.server.common.data.ShortCustomerInfo; |
38 | +import org.thingsboard.server.common.data.Tenant; | |
39 | +import org.thingsboard.server.common.data.User; | |
34 | 40 | import org.thingsboard.server.common.data.audit.ActionType; |
35 | 41 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
36 | 42 | import org.thingsboard.server.common.data.id.CustomerId; |
... | ... | @@ -39,7 +45,9 @@ import org.thingsboard.server.common.data.id.TenantId; |
39 | 45 | import org.thingsboard.server.common.data.page.PageData; |
40 | 46 | import org.thingsboard.server.common.data.page.PageLink; |
41 | 47 | import org.thingsboard.server.common.data.page.TimePageLink; |
48 | +import org.thingsboard.server.dao.util.mapping.JacksonUtil; | |
42 | 49 | import org.thingsboard.server.queue.util.TbCoreComponent; |
50 | +import org.thingsboard.server.service.security.model.SecurityUser; | |
43 | 51 | import org.thingsboard.server.service.security.permission.Operation; |
44 | 52 | import org.thingsboard.server.service.security.permission.Resource; |
45 | 53 | |
... | ... | @@ -53,6 +61,9 @@ public class DashboardController extends BaseController { |
53 | 61 | |
54 | 62 | public static final String DASHBOARD_ID = "dashboardId"; |
55 | 63 | |
64 | + private static final String HOME_DASHBOARD_ID = "homeDashboardId"; | |
65 | + private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; | |
66 | + | |
56 | 67 | @Value("${dashboard.max_datapoints_limit}") |
57 | 68 | private long maxDatapointsLimit; |
58 | 69 | |
... | ... | @@ -472,4 +483,100 @@ public class DashboardController extends BaseController { |
472 | 483 | throw handleException(e); |
473 | 484 | } |
474 | 485 | } |
486 | + | |
487 | + @PreAuthorize("isAuthenticated()") | |
488 | + @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) | |
489 | + @ResponseBody | |
490 | + public HomeDashboard getHomeDashboard() throws ThingsboardException { | |
491 | + try { | |
492 | + SecurityUser securityUser = getCurrentUser(); | |
493 | + if (securityUser.isSystemAdmin()) { | |
494 | + return null; | |
495 | + } | |
496 | + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); | |
497 | + JsonNode additionalInfo = user.getAdditionalInfo(); | |
498 | + HomeDashboard homeDashboard; | |
499 | + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); | |
500 | + if (homeDashboard == null) { | |
501 | + if (securityUser.isCustomerUser()) { | |
502 | + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); | |
503 | + additionalInfo = customer.getAdditionalInfo(); | |
504 | + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); | |
505 | + } | |
506 | + if (homeDashboard == null) { | |
507 | + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); | |
508 | + additionalInfo = tenant.getAdditionalInfo(); | |
509 | + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); | |
510 | + } | |
511 | + } | |
512 | + return homeDashboard; | |
513 | + } catch (Exception e) { | |
514 | + throw handleException(e); | |
515 | + } | |
516 | + } | |
517 | + | |
518 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
519 | + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) | |
520 | + @ResponseBody | |
521 | + public HomeDashboardInfo getTenantHomeDashboardInfo() throws ThingsboardException { | |
522 | + try { | |
523 | + Tenant tenant = tenantService.findTenantById(getTenantId()); | |
524 | + JsonNode additionalInfo = tenant.getAdditionalInfo(); | |
525 | + DashboardId dashboardId = null; | |
526 | + boolean hideDashboardToolbar = true; | |
527 | + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { | |
528 | + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); | |
529 | + dashboardId = new DashboardId(toUUID(strDashboardId)); | |
530 | + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { | |
531 | + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); | |
532 | + } | |
533 | + } | |
534 | + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); | |
535 | + } catch (Exception e) { | |
536 | + throw handleException(e); | |
537 | + } | |
538 | + } | |
539 | + | |
540 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | |
541 | + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) | |
542 | + @ResponseStatus(value = HttpStatus.OK) | |
543 | + public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { | |
544 | + try { | |
545 | + if (homeDashboardInfo.getDashboardId() != null) { | |
546 | + checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); | |
547 | + } | |
548 | + Tenant tenant = tenantService.findTenantById(getTenantId()); | |
549 | + JsonNode additionalInfo = tenant.getAdditionalInfo(); | |
550 | + if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { | |
551 | + additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode(); | |
552 | + } | |
553 | + if (homeDashboardInfo.getDashboardId() != null) { | |
554 | + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString()); | |
555 | + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar()); | |
556 | + } else { | |
557 | + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID); | |
558 | + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR); | |
559 | + } | |
560 | + tenant.setAdditionalInfo(additionalInfo); | |
561 | + tenantService.saveTenant(tenant); | |
562 | + } catch (Exception e) { | |
563 | + throw handleException(e); | |
564 | + } | |
565 | + } | |
566 | + | |
567 | + private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { | |
568 | + try { | |
569 | + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { | |
570 | + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); | |
571 | + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); | |
572 | + Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ); | |
573 | + boolean hideDashboardToolbar = true; | |
574 | + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { | |
575 | + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); | |
576 | + } | |
577 | + return new HomeDashboard(dashboard, hideDashboardToolbar); | |
578 | + } | |
579 | + } catch (Exception e) {} | |
580 | + return null; | |
581 | + } | |
475 | 582 | } | ... | ... |
... | ... | @@ -185,6 +185,8 @@ public class ThingsboardInstallService { |
185 | 185 | case "3.2.0": |
186 | 186 | log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ..."); |
187 | 187 | databaseEntitiesUpgradeService.upgradeDatabase("3.2.0"); |
188 | + case "3.2.1": | |
189 | + log.info("Upgrading ThingsBoard from version 3.2.1 to 3.3.0 ..."); | |
188 | 190 | log.info("Updating system data..."); |
189 | 191 | systemDataLoaderService.updateSystemWidgets(); |
190 | 192 | break; | ... | ... |
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
... | ... | @@ -197,7 +197,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
197 | 197 | generalSettings.setKey("general"); |
198 | 198 | ObjectNode node = objectMapper.createObjectNode(); |
199 | 199 | node.put("baseUrl", "http://localhost:8080"); |
200 | - node.put("prohibitDifferentUrl", true); | |
200 | + node.put("prohibitDifferentUrl", false); | |
201 | 201 | generalSettings.setJsonValue(node); |
202 | 202 | adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); |
203 | 203 | |
... | ... | @@ -438,6 +438,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { |
438 | 438 | this.deleteSystemWidgetBundle("input_widgets"); |
439 | 439 | this.deleteSystemWidgetBundle("date"); |
440 | 440 | this.deleteSystemWidgetBundle("entity_admin_widgets"); |
441 | + this.deleteSystemWidgetBundle("navigation_widgets"); | |
441 | 442 | installScripts.loadSystemWidgets(); |
442 | 443 | } |
443 | 444 | ... | ... |
... | ... | @@ -15,11 +15,20 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.server.service.install; |
17 | 17 | |
18 | +import lombok.extern.slf4j.Slf4j; | |
18 | 19 | import org.springframework.context.annotation.Profile; |
19 | 20 | import org.springframework.stereotype.Service; |
20 | 21 | import org.thingsboard.server.dao.util.HsqlDao; |
21 | 22 | |
23 | +import java.nio.charset.Charset; | |
24 | +import java.nio.file.Files; | |
25 | +import java.nio.file.Path; | |
26 | +import java.nio.file.Paths; | |
27 | +import java.sql.Connection; | |
28 | +import java.sql.DriverManager; | |
29 | + | |
22 | 30 | @Service |
31 | +@Slf4j | |
23 | 32 | @HsqlDao |
24 | 33 | @Profile("install") |
25 | 34 | public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService |
... | ... | @@ -27,5 +36,21 @@ public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSe |
27 | 36 | protected HsqlEntityDatabaseSchemaService() { |
28 | 37 | super("schema-entities-hsql.sql", "schema-entities-idx.sql"); |
29 | 38 | } |
39 | + | |
40 | + private final String schemaTypesSql = "schema-types-hsql.sql"; | |
41 | + | |
42 | + @Override | |
43 | + public void createDatabaseSchema(boolean createIndexes) throws Exception { | |
44 | + | |
45 | + log.info("Installing SQL DataBase types part: " + schemaTypesSql); | |
46 | + | |
47 | + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaTypesSql); | |
48 | + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { | |
49 | + String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8")); | |
50 | + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema | |
51 | + } | |
52 | + | |
53 | + super.createDatabaseSchema(createIndexes); | |
54 | + } | |
30 | 55 | } |
31 | 56 | ... | ... |
... | ... | @@ -30,7 +30,7 @@ import java.sql.SQLException; |
30 | 30 | @Slf4j |
31 | 31 | public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService { |
32 | 32 | |
33 | - private static final String SQL_DIR = "sql"; | |
33 | + protected static final String SQL_DIR = "sql"; | |
34 | 34 | |
35 | 35 | @Value("${spring.datasource.url}") |
36 | 36 | protected String dbUrl; |
... | ... | @@ -42,7 +42,7 @@ public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchema |
42 | 42 | protected String dbPassword; |
43 | 43 | |
44 | 44 | @Autowired |
45 | - private InstallScripts installScripts; | |
45 | + protected InstallScripts installScripts; | |
46 | 46 | |
47 | 47 | private final String schemaSql; |
48 | 48 | private final String schemaIdxSql; | ... | ... |
... | ... | @@ -59,6 +59,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; |
59 | 59 | import org.thingsboard.server.queue.util.TbCoreComponent; |
60 | 60 | import org.thingsboard.server.service.queue.TbClusterService; |
61 | 61 | import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
62 | +import org.thingsboard.server.utils.EventDeduplicationExecutor; | |
62 | 63 | |
63 | 64 | import javax.annotation.Nullable; |
64 | 65 | import javax.annotation.PostConstruct; |
... | ... | @@ -126,13 +127,13 @@ public class DefaultDeviceStateService implements DeviceStateService { |
126 | 127 | @Getter |
127 | 128 | private int initFetchPackSize; |
128 | 129 | |
129 | - private volatile boolean clusterUpdatePending = false; | |
130 | - | |
131 | 130 | private ListeningScheduledExecutorService queueExecutor; |
132 | 131 | private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>(); |
133 | 132 | private final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); |
134 | 133 | private final ConcurrentMap<DeviceId, Long> deviceLastReportedActivity = new ConcurrentHashMap<>(); |
135 | 134 | private final ConcurrentMap<DeviceId, Long> deviceLastSavedActivity = new ConcurrentHashMap<>(); |
135 | + private volatile EventDeduplicationExecutor<Set<TopicPartitionInfo>> deduplicationExecutor; | |
136 | + | |
136 | 137 | |
137 | 138 | public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, |
138 | 139 | AttributesService attributesService, TimeseriesService tsService, |
... | ... | @@ -155,6 +156,7 @@ public class DefaultDeviceStateService implements DeviceStateService { |
155 | 156 | // Should be always single threaded due to absence of locks. |
156 | 157 | queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state"))); |
157 | 158 | queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); |
159 | + deduplicationExecutor = new EventDeduplicationExecutor<>(DefaultDeviceStateService.class.getSimpleName(), queueExecutor, this::initStateFromDB); | |
158 | 160 | } |
159 | 161 | |
160 | 162 | @PreDestroy |
... | ... | @@ -292,25 +294,14 @@ public class DefaultDeviceStateService implements DeviceStateService { |
292 | 294 | } |
293 | 295 | } |
294 | 296 | |
295 | - volatile Set<TopicPartitionInfo> pendingPartitions; | |
296 | - | |
297 | 297 | @Override |
298 | 298 | public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { |
299 | 299 | if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { |
300 | - synchronized (this) { | |
301 | - pendingPartitions = partitionChangeEvent.getPartitions(); | |
302 | - if (!clusterUpdatePending) { | |
303 | - clusterUpdatePending = true; | |
304 | - queueExecutor.submit(() -> { | |
305 | - clusterUpdatePending = false; | |
306 | - initStateFromDB(); | |
307 | - }); | |
308 | - } | |
309 | - } | |
300 | + deduplicationExecutor.submit(partitionChangeEvent.getPartitions()); | |
310 | 301 | } |
311 | 302 | } |
312 | 303 | |
313 | - private void initStateFromDB() { | |
304 | + private void initStateFromDB(Set<TopicPartitionInfo> pendingPartitions) { | |
314 | 305 | try { |
315 | 306 | log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet()); |
316 | 307 | log.info("NEW PARTITIONS: {}", pendingPartitions); | ... | ... |
... | ... | @@ -302,7 +302,9 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc |
302 | 302 | Map<Integer, TbAbstractDataSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); |
303 | 303 | TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, |
304 | 304 | attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); |
305 | - ctx.setAndResolveQuery(cmd.getQuery()); | |
305 | + if (cmd.getQuery() != null) { | |
306 | + ctx.setAndResolveQuery(cmd.getQuery()); | |
307 | + } | |
306 | 308 | sessionSubs.put(cmd.getCmdId(), ctx); |
307 | 309 | return ctx; |
308 | 310 | } | ... | ... |
... | ... | @@ -107,7 +107,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends |
107 | 107 | public void setAndResolveQuery(T query) { |
108 | 108 | dynamicValues.clear(); |
109 | 109 | this.query = query; |
110 | - if (query.getKeyFilters() != null) { | |
110 | + if (query != null && query.getKeyFilters() != null) { | |
111 | 111 | for (KeyFilter filter : query.getKeyFilters()) { |
112 | 112 | registerDynamicValues(filter.getPredicate()); |
113 | 113 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.utils; | |
17 | + | |
18 | +import lombok.extern.slf4j.Slf4j; | |
19 | + | |
20 | +import java.util.concurrent.Executor; | |
21 | +import java.util.concurrent.ExecutorService; | |
22 | +import java.util.function.Consumer; | |
23 | + | |
24 | +/** | |
25 | + * This class deduplicate executions of the specified function. | |
26 | + * Useful in cluster mode, when you get event about partition change multiple times. | |
27 | + * Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and | |
28 | + * later, once the processing of first event is done, process last pending task. | |
29 | + * | |
30 | + * @param <P> parameters of the function | |
31 | + */ | |
32 | +@Slf4j | |
33 | +public class EventDeduplicationExecutor<P> { | |
34 | + private final String name; | |
35 | + private final ExecutorService executor; | |
36 | + private final Consumer<P> function; | |
37 | + private P pendingTask; | |
38 | + private boolean busy; | |
39 | + | |
40 | + public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer<P> function) { | |
41 | + this.name = name; | |
42 | + this.executor = executor; | |
43 | + this.function = function; | |
44 | + } | |
45 | + | |
46 | + public void submit(P params) { | |
47 | + log.info("[{}] Going to submit: {}", name, params); | |
48 | + synchronized (EventDeduplicationExecutor.this) { | |
49 | + if (!busy) { | |
50 | + busy = true; | |
51 | + pendingTask = null; | |
52 | + try { | |
53 | + log.info("[{}] Submitting task: {}", name, params); | |
54 | + executor.submit(() -> { | |
55 | + try { | |
56 | + log.info("[{}] Executing task: {}", name, params); | |
57 | + function.accept(params); | |
58 | + } catch (Throwable e) { | |
59 | + log.warn("[{}] Failed to process task with parameters: {}", name, params, e); | |
60 | + throw e; | |
61 | + } finally { | |
62 | + unlockAndProcessIfAny(); | |
63 | + } | |
64 | + }); | |
65 | + } catch (Throwable e) { | |
66 | + log.warn("[{}] Failed to submit task with parameters: {}", name, params, e); | |
67 | + unlockAndProcessIfAny(); | |
68 | + throw e; | |
69 | + } | |
70 | + } else { | |
71 | + log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params); | |
72 | + pendingTask = params; | |
73 | + } | |
74 | + } | |
75 | + } | |
76 | + | |
77 | + private void unlockAndProcessIfAny() { | |
78 | + synchronized (EventDeduplicationExecutor.this) { | |
79 | + busy = false; | |
80 | + if (pendingTask != null) { | |
81 | + submit(pendingTask); | |
82 | + } | |
83 | + } | |
84 | + } | |
85 | +} | ... | ... |
application/src/test/java/org/thingsboard/server/util/EventDeduplicationExecutorTest.java
0 → 100644
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.util; | |
17 | + | |
18 | +import com.google.common.util.concurrent.MoreExecutors; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.junit.Test; | |
21 | +import org.junit.runner.RunWith; | |
22 | +import org.mockito.Mockito; | |
23 | +import org.mockito.runners.MockitoJUnitRunner; | |
24 | +import org.thingsboard.server.utils.EventDeduplicationExecutor; | |
25 | + | |
26 | +import java.util.concurrent.ExecutorService; | |
27 | +import java.util.concurrent.Executors; | |
28 | +import java.util.function.Consumer; | |
29 | + | |
30 | +@Slf4j | |
31 | +@RunWith(MockitoJUnitRunner.class) | |
32 | +public class EventDeduplicationExecutorTest { | |
33 | + | |
34 | + @Test | |
35 | + public void testSimpleFlowSameThread() throws InterruptedException { | |
36 | + simpleFlow(MoreExecutors.newDirectExecutorService()); | |
37 | + } | |
38 | + | |
39 | + @Test | |
40 | + public void testPeriodicFlowSameThread() throws InterruptedException { | |
41 | + periodicFlow(MoreExecutors.newDirectExecutorService()); | |
42 | + } | |
43 | + | |
44 | + @Test | |
45 | + public void testExceptionFlowSameThread() throws InterruptedException { | |
46 | + exceptionFlow(MoreExecutors.newDirectExecutorService()); | |
47 | + } | |
48 | + | |
49 | + @Test | |
50 | + public void testSimpleFlowSingleThread() throws InterruptedException { | |
51 | + simpleFlow(Executors.newSingleThreadExecutor()); | |
52 | + } | |
53 | + | |
54 | + @Test | |
55 | + public void testPeriodicFlowSingleThread() throws InterruptedException { | |
56 | + periodicFlow(Executors.newSingleThreadExecutor()); | |
57 | + } | |
58 | + | |
59 | + @Test | |
60 | + public void testExceptionFlowSingleThread() throws InterruptedException { | |
61 | + exceptionFlow(Executors.newSingleThreadExecutor()); | |
62 | + } | |
63 | + | |
64 | + @Test | |
65 | + public void testSimpleFlowMultiThread() throws InterruptedException { | |
66 | + simpleFlow(Executors.newFixedThreadPool(3)); | |
67 | + } | |
68 | + | |
69 | + @Test | |
70 | + public void testPeriodicFlowMultiThread() throws InterruptedException { | |
71 | + periodicFlow(Executors.newFixedThreadPool(3)); | |
72 | + } | |
73 | + | |
74 | + @Test | |
75 | + public void testExceptionFlowMultiThread() throws InterruptedException { | |
76 | + exceptionFlow(Executors.newFixedThreadPool(3)); | |
77 | + } | |
78 | + | |
79 | + private void simpleFlow(ExecutorService executorService) throws InterruptedException { | |
80 | + try { | |
81 | + Consumer<String> function = Mockito.spy(StringConsumer.class); | |
82 | + EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); | |
83 | + | |
84 | + String params1 = "params1"; | |
85 | + String params2 = "params2"; | |
86 | + String params3 = "params3"; | |
87 | + | |
88 | + executor.submit(params1); | |
89 | + executor.submit(params2); | |
90 | + executor.submit(params3); | |
91 | + Thread.sleep(500); | |
92 | + Mockito.verify(function).accept(params1); | |
93 | + Mockito.verify(function).accept(params3); | |
94 | + } finally { | |
95 | + executorService.shutdownNow(); | |
96 | + } | |
97 | + } | |
98 | + | |
99 | + private void periodicFlow(ExecutorService executorService) throws InterruptedException { | |
100 | + try { | |
101 | + Consumer<String> function = Mockito.spy(StringConsumer.class); | |
102 | + EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); | |
103 | + | |
104 | + String params1 = "params1"; | |
105 | + String params2 = "params2"; | |
106 | + String params3 = "params3"; | |
107 | + | |
108 | + executor.submit(params1); | |
109 | + Thread.sleep(500); | |
110 | + executor.submit(params2); | |
111 | + Thread.sleep(500); | |
112 | + executor.submit(params3); | |
113 | + Thread.sleep(500); | |
114 | + Mockito.verify(function).accept(params1); | |
115 | + Mockito.verify(function).accept(params2); | |
116 | + Mockito.verify(function).accept(params3); | |
117 | + } finally { | |
118 | + executorService.shutdownNow(); | |
119 | + } | |
120 | + } | |
121 | + | |
122 | + private void exceptionFlow(ExecutorService executorService) throws InterruptedException { | |
123 | + try { | |
124 | + Consumer<String> function = Mockito.spy(StringConsumer.class); | |
125 | + EventDeduplicationExecutor<String> executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); | |
126 | + | |
127 | + String params1 = "params1"; | |
128 | + String params2 = "params2"; | |
129 | + String params3 = "params3"; | |
130 | + | |
131 | + Mockito.doThrow(new RuntimeException()).when(function).accept("params1"); | |
132 | + executor.submit(params1); | |
133 | + executor.submit(params2); | |
134 | + Thread.sleep(500); | |
135 | + executor.submit(params3); | |
136 | + Thread.sleep(500); | |
137 | + Mockito.verify(function).accept(params2); | |
138 | + Mockito.verify(function).accept(params3); | |
139 | + } finally { | |
140 | + executorService.shutdownNow(); | |
141 | + } | |
142 | + } | |
143 | + | |
144 | + public static class StringConsumer implements Consumer<String> { | |
145 | + @Override | |
146 | + public void accept(String s) { | |
147 | + try { | |
148 | + Thread.sleep(100); | |
149 | + } catch (InterruptedException e) { | |
150 | + throw new RuntimeException(e); | |
151 | + } | |
152 | + } | |
153 | + } | |
154 | + | |
155 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data; | |
17 | + | |
18 | +import lombok.Data; | |
19 | + | |
20 | +@Data | |
21 | +public class HomeDashboard extends Dashboard { | |
22 | + | |
23 | + private boolean hideDashboardToolbar; | |
24 | + | |
25 | + public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) { | |
26 | + super(dashboard); | |
27 | + this.hideDashboardToolbar = hideDashboardToolbar; | |
28 | + } | |
29 | + | |
30 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package org.thingsboard.server.common.data; | |
17 | + | |
18 | +import lombok.AllArgsConstructor; | |
19 | +import lombok.Data; | |
20 | +import org.thingsboard.server.common.data.id.DashboardId; | |
21 | + | |
22 | +@Data | |
23 | +@AllArgsConstructor | |
24 | +public class HomeDashboardInfo { | |
25 | + private DashboardId dashboardId; | |
26 | + private boolean hideDashboardToolbar; | |
27 | +} | ... | ... |
... | ... | @@ -19,16 +19,18 @@ import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.server.common.data.alarm.Alarm; |
20 | 20 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
21 | 21 | import org.thingsboard.server.common.data.alarm.AlarmQuery; |
22 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | |
23 | +import org.thingsboard.server.common.data.alarm.AlarmStatus; | |
22 | 24 | import org.thingsboard.server.common.data.id.CustomerId; |
23 | 25 | import org.thingsboard.server.common.data.id.EntityId; |
24 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
25 | 27 | import org.thingsboard.server.common.data.page.PageData; |
26 | 28 | import org.thingsboard.server.common.data.query.AlarmData; |
27 | -import org.thingsboard.server.common.data.query.AlarmDataPageLink; | |
28 | 29 | import org.thingsboard.server.common.data.query.AlarmDataQuery; |
29 | 30 | import org.thingsboard.server.dao.Dao; |
30 | 31 | |
31 | 32 | import java.util.Collection; |
33 | +import java.util.Set; | |
32 | 34 | import java.util.UUID; |
33 | 35 | |
34 | 36 | /** |
... | ... | @@ -48,4 +50,6 @@ public interface AlarmDao extends Dao<Alarm> { |
48 | 50 | |
49 | 51 | PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, |
50 | 52 | AlarmDataQuery query, Collection<EntityId> orderedEntityIds); |
53 | + | |
54 | + Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status); | |
51 | 55 | } | ... | ... |
... | ... | @@ -39,10 +39,10 @@ import org.thingsboard.server.common.data.id.CustomerId; |
39 | 39 | import org.thingsboard.server.common.data.id.EntityId; |
40 | 40 | import org.thingsboard.server.common.data.id.TenantId; |
41 | 41 | import org.thingsboard.server.common.data.page.PageData; |
42 | -import org.thingsboard.server.common.data.page.TimePageLink; | |
43 | 42 | import org.thingsboard.server.common.data.query.AlarmData; |
44 | 43 | import org.thingsboard.server.common.data.query.AlarmDataPageLink; |
45 | 44 | import org.thingsboard.server.common.data.query.AlarmDataQuery; |
45 | +import org.thingsboard.server.common.data.query.DeviceTypeFilter; | |
46 | 46 | import org.thingsboard.server.common.data.relation.EntityRelation; |
47 | 47 | import org.thingsboard.server.common.data.relation.EntityRelationsQuery; |
48 | 48 | import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
... | ... | @@ -60,7 +60,6 @@ import javax.annotation.PreDestroy; |
60 | 60 | import java.util.ArrayList; |
61 | 61 | import java.util.Collection; |
62 | 62 | import java.util.Collections; |
63 | -import java.util.Comparator; | |
64 | 63 | import java.util.LinkedHashSet; |
65 | 64 | import java.util.List; |
66 | 65 | import java.util.Set; |
... | ... | @@ -316,37 +315,16 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ |
316 | 315 | @Override |
317 | 316 | public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, |
318 | 317 | AlarmStatus alarmStatus) { |
319 | - TimePageLink nextPageLink = new TimePageLink(100); | |
320 | - boolean hasNext = true; | |
321 | - AlarmSeverity highestSeverity = null; | |
322 | - AlarmQuery query; | |
323 | - while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { | |
324 | - query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false, null); | |
325 | - PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query); | |
326 | - if (alarms.hasNext()) { | |
327 | - nextPageLink = nextPageLink.nextPageLink(); | |
328 | - } | |
329 | - AlarmSeverity severity = detectHighestSeverity(alarms.getData()); | |
330 | - if (severity == null) { | |
331 | - continue; | |
332 | - } | |
333 | - if (severity == AlarmSeverity.CRITICAL || highestSeverity == null) { | |
334 | - highestSeverity = severity; | |
335 | - } else { | |
336 | - highestSeverity = highestSeverity.compareTo(severity) < 0 ? highestSeverity : severity; | |
337 | - } | |
318 | + Set<AlarmStatus> statusList = null; | |
319 | + if (alarmSearchStatus != null) { | |
320 | + statusList = alarmSearchStatus.getStatuses(); | |
321 | + } else if (alarmStatus != null) { | |
322 | + statusList = Collections.singleton(alarmStatus); | |
338 | 323 | } |
339 | - return highestSeverity; | |
340 | - } | |
341 | 324 | |
342 | - private AlarmSeverity detectHighestSeverity(List<AlarmInfo> alarms) { | |
343 | - if (!alarms.isEmpty()) { | |
344 | - List<AlarmInfo> sorted = new ArrayList(alarms); | |
345 | - sorted.sort(Comparator.comparing(Alarm::getSeverity)); | |
346 | - return sorted.get(0).getSeverity(); | |
347 | - } else { | |
348 | - return null; | |
349 | - } | |
325 | + Set<AlarmSeverity> alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList); | |
326 | + | |
327 | + return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); | |
350 | 328 | } |
351 | 329 | |
352 | 330 | private void deleteRelation(TenantId tenantId, EntityRelation alarmRelation) { | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; |
20 | 20 | import org.springframework.data.jpa.repository.Query; |
21 | 21 | import org.springframework.data.repository.CrudRepository; |
22 | 22 | import org.springframework.data.repository.query.Param; |
23 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | |
23 | 24 | import org.thingsboard.server.common.data.alarm.AlarmStatus; |
24 | 25 | import org.thingsboard.server.dao.model.sql.AlarmEntity; |
25 | 26 | import org.thingsboard.server.dao.model.sql.AlarmInfoEntity; |
... | ... | @@ -75,4 +76,12 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> { |
75 | 76 | @Param("searchText") String searchText, |
76 | 77 | Pageable pageable); |
77 | 78 | |
79 | + @Query("SELECT alarm.severity FROM AlarmEntity alarm" + | |
80 | + " WHERE alarm.tenantId = :tenantId" + | |
81 | + " AND alarm.originatorId = :entityId" + | |
82 | + " AND ((:status) IS NULL OR alarm.status in (:status))") | |
83 | + Set<AlarmSeverity> findAlarmSeverities(@Param("tenantId") UUID tenantId, | |
84 | + @Param("entityId") UUID entityId, | |
85 | + @Param("status") Set<AlarmStatus> status); | |
86 | + | |
78 | 87 | } | ... | ... |
... | ... | @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; |
24 | 24 | import org.thingsboard.server.common.data.alarm.Alarm; |
25 | 25 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
26 | 26 | import org.thingsboard.server.common.data.alarm.AlarmQuery; |
27 | +import org.thingsboard.server.common.data.alarm.AlarmSeverity; | |
27 | 28 | import org.thingsboard.server.common.data.alarm.AlarmStatus; |
28 | 29 | import org.thingsboard.server.common.data.id.CustomerId; |
29 | 30 | import org.thingsboard.server.common.data.id.EntityId; |
... | ... | @@ -120,4 +121,9 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A |
120 | 121 | public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { |
121 | 122 | return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds); |
122 | 123 | } |
124 | + | |
125 | + @Override | |
126 | + public Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status) { | |
127 | + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), status); | |
128 | + } | |
123 | 129 | } | ... | ... |
... | ... | @@ -83,7 +83,7 @@ public class EntityDataAdapter { |
83 | 83 | if (value != null) { |
84 | 84 | String strVal = value.toString(); |
85 | 85 | // check number |
86 | - if (strVal.length() > 0 && NumberUtils.isParsable(strVal)) { | |
86 | + if (NumberUtils.isNumber(strVal)) { | |
87 | 87 | try { |
88 | 88 | long longVal = Long.parseLong(strVal); |
89 | 89 | return Long.toString(longVal); | ... | ... |
... | ... | @@ -116,16 +116,16 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD |
116 | 116 | super.startExecutor(); |
117 | 117 | if (!isInstall()) { |
118 | 118 | getFetchStmt(Aggregation.NONE, DESC_ORDER); |
119 | - Optional<NoSqlTsPartitionDate> partition = NoSqlTsPartitionDate.parse(partitioning); | |
120 | - if (partition.isPresent()) { | |
121 | - tsFormat = partition.get(); | |
122 | - if (!isFixedPartitioning() && partitionsCacheSize > 0) { | |
123 | - cassandraTsPartitionsCache = new CassandraTsPartitionsCache(partitionsCacheSize); | |
124 | - } | |
125 | - } else { | |
126 | - log.warn("Incorrect configuration of partitioning {}", partitioning); | |
127 | - throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | |
119 | + } | |
120 | + Optional<NoSqlTsPartitionDate> partition = NoSqlTsPartitionDate.parse(partitioning); | |
121 | + if (partition.isPresent()) { | |
122 | + tsFormat = partition.get(); | |
123 | + if (!isFixedPartitioning() && partitionsCacheSize > 0) { | |
124 | + cassandraTsPartitionsCache = new CassandraTsPartitionsCache(partitionsCacheSize); | |
128 | 125 | } |
126 | + } else { | |
127 | + log.warn("Incorrect configuration of partitioning {}", partitioning); | |
128 | + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); | |
129 | 129 | } |
130 | 130 | } |
131 | 131 | ... | ... |
... | ... | @@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS customer ( |
114 | 114 | CREATE TABLE IF NOT EXISTS dashboard ( |
115 | 115 | id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, |
116 | 116 | created_time bigint NOT NULL, |
117 | - configuration varchar, | |
117 | + configuration varchar(10000000), | |
118 | 118 | assigned_customers varchar(1000000), |
119 | 119 | search_text varchar(255), |
120 | 120 | tenant_id uuid, | ... | ... |
... | ... | @@ -355,6 +355,62 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { |
355 | 355 | } |
356 | 356 | |
357 | 357 | @Test |
358 | + public void testFindHighestAlarmSeverity() throws ExecutionException, InterruptedException { | |
359 | + Customer customer = new Customer(); | |
360 | + customer.setTitle("TestCustomer"); | |
361 | + customer.setTenantId(tenantId); | |
362 | + customer = customerService.saveCustomer(customer); | |
363 | + | |
364 | + Device customerDevice = new Device(); | |
365 | + customerDevice.setName("TestCustomerDevice"); | |
366 | + customerDevice.setType("default"); | |
367 | + customerDevice.setTenantId(tenantId); | |
368 | + customerDevice.setCustomerId(customer.getId()); | |
369 | + customerDevice = deviceService.saveDevice(customerDevice); | |
370 | + | |
371 | + // no one alarms was created | |
372 | + Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); | |
373 | + | |
374 | + Alarm alarm1 = Alarm.builder() | |
375 | + .tenantId(tenantId) | |
376 | + .originator(customerDevice.getId()) | |
377 | + .type(TEST_ALARM) | |
378 | + .severity(AlarmSeverity.MAJOR) | |
379 | + .status(AlarmStatus.ACTIVE_UNACK) | |
380 | + .startTs(System.currentTimeMillis()) | |
381 | + .build(); | |
382 | + alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarm(); | |
383 | + alarmService.clearAlarm(tenantId, alarm1.getId(), null, System.currentTimeMillis()).get(); | |
384 | + | |
385 | + Alarm alarm2 = Alarm.builder() | |
386 | + .tenantId(tenantId) | |
387 | + .originator(customerDevice.getId()) | |
388 | + .type(TEST_ALARM) | |
389 | + .severity(AlarmSeverity.MINOR) | |
390 | + .status(AlarmStatus.ACTIVE_ACK) | |
391 | + .startTs(System.currentTimeMillis()) | |
392 | + .build(); | |
393 | + alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarm(); | |
394 | + alarmService.clearAlarm(tenantId, alarm2.getId(), null, System.currentTimeMillis()).get(); | |
395 | + | |
396 | + Alarm alarm3 = Alarm.builder() | |
397 | + .tenantId(tenantId) | |
398 | + .originator(customerDevice.getId()) | |
399 | + .type(TEST_ALARM) | |
400 | + .severity(AlarmSeverity.CRITICAL) | |
401 | + .status(AlarmStatus.ACTIVE_ACK) | |
402 | + .startTs(System.currentTimeMillis()) | |
403 | + .build(); | |
404 | + alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm(); | |
405 | + | |
406 | + Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null)); | |
407 | + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); | |
408 | + Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK)); | |
409 | + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null)); | |
410 | + Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK)); | |
411 | + } | |
412 | + | |
413 | + @Test | |
358 | 414 | public void testFindAlarmUsingAlarmDataQuery() throws ExecutionException, InterruptedException { |
359 | 415 | AssetId parentId = new AssetId(Uuids.timeBased()); |
360 | 416 | AssetId parentId2 = new AssetId(Uuids.timeBased()); | ... | ... |
... | ... | @@ -827,10 +827,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
827 | 827 | .getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); |
828 | 828 | } |
829 | 829 | List<String> deviceTemperatures = temperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList()); |
830 | - if (DaoTestUtil.getSqlDbType(template) == SqlDbType.H2) { | |
831 | - // in H2 double values are stored with E0 in the end of the string | |
832 | - loadedTemperatures = loadedTemperatures.stream().map(s -> s.substring(0, s.length() - 2)).collect(Collectors.toList()); | |
833 | - } | |
830 | + | |
834 | 831 | Assert.assertEquals(deviceTemperatures, loadedTemperatures); |
835 | 832 | |
836 | 833 | pageLink = new EntityDataPageLink(10, 0, null, sortOrder); |
... | ... | @@ -858,10 +855,6 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { |
858 | 855 | entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).collect(Collectors.toList()); |
859 | 856 | List<String> deviceHighTemperatures = highTemperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList()); |
860 | 857 | |
861 | - if (DaoTestUtil.getSqlDbType(template) == SqlDbType.H2) { | |
862 | - // in H2 double values are stored with E0 in the end of the string | |
863 | - loadedHighTemperatures = loadedHighTemperatures.stream().map(s -> s.substring(0, s.length() - 2)).collect(Collectors.toList()); | |
864 | - } | |
865 | 858 | Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures); |
866 | 859 | |
867 | 860 | deviceService.deleteDevicesByTenantId(tenantId); | ... | ... |
... | ... | @@ -18,7 +18,7 @@ FROM thingsboard/openjdk8 |
18 | 18 | |
19 | 19 | RUN apt-get update |
20 | 20 | RUN apt-get install -y curl nmap procps |
21 | -RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null | |
21 | +RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ sid-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null | |
22 | 22 | RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - |
23 | 23 | RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null |
24 | 24 | RUN curl -L https://www.apache.org/dist/cassandra/KEYS | apt-key add - | ... | ... |
... | ... | @@ -18,7 +18,7 @@ FROM thingsboard/openjdk8 |
18 | 18 | |
19 | 19 | RUN apt-get update |
20 | 20 | RUN apt-get install -y curl |
21 | -RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null | |
21 | +RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ sid-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null | |
22 | 22 | RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - |
23 | 23 | ENV PG_MAJOR 11 |
24 | 24 | RUN apt-get update | ... | ... |
... | ... | @@ -1189,7 +1189,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { |
1189 | 1189 | |
1190 | 1190 | public List<EntitySubtype> getDeviceTypes() { |
1191 | 1191 | return restTemplate.exchange( |
1192 | - baseURL + "/api/devices", | |
1192 | + baseURL + "/api/device/types", | |
1193 | 1193 | HttpMethod.GET, |
1194 | 1194 | HttpEntity.EMPTY, |
1195 | 1195 | new ParameterizedTypeReference<List<EntitySubtype>>() { | ... | ... |
... | ... | @@ -61,4 +61,12 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA |
61 | 61 | configuration.setCredentials(new AnonymousCredentials()); |
62 | 62 | return configuration; |
63 | 63 | } |
64 | + | |
65 | + public ClientCredentials getCredentials() { | |
66 | + if (this.credentials == null) { | |
67 | + return new AnonymousCredentials(); | |
68 | + } else { | |
69 | + return this.credentials; | |
70 | + } | |
71 | + } | |
64 | 72 | } | ... | ... |
... | ... | @@ -53,8 +53,8 @@ export class AliasController implements IAliasController { |
53 | 53 | private stateControllerHolder: StateControllerHolder, |
54 | 54 | private origEntityAliases: EntityAliases, |
55 | 55 | private origFilters: Filters) { |
56 | - this.entityAliases = deepClone(this.origEntityAliases); | |
57 | - this.filters = deepClone(this.origFilters); | |
56 | + this.entityAliases = deepClone(this.origEntityAliases) || {}; | |
57 | + this.filters = deepClone(this.origFilters) || {}; | |
58 | 58 | this.userFilters = {}; |
59 | 59 | } |
60 | 60 | ... | ... |
... | ... | @@ -15,7 +15,7 @@ |
15 | 15 | /// |
16 | 16 | |
17 | 17 | import { Injectable, NgZone } from '@angular/core'; |
18 | -import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router'; | |
18 | +import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router'; | |
19 | 19 | import { AuthService } from '../auth/auth.service'; |
20 | 20 | import { select, Store } from '@ngrx/store'; |
21 | 21 | import { AppState } from '../core.state'; |
... | ... | @@ -28,6 +28,7 @@ import { Authority } from '@shared/models/authority.enum'; |
28 | 28 | import { DialogService } from '@core/services/dialog.service'; |
29 | 29 | import { TranslateService } from '@ngx-translate/core'; |
30 | 30 | import { UtilsService } from '@core/services/utils.service'; |
31 | +import { isObject } from '@core/utils'; | |
31 | 32 | |
32 | 33 | @Injectable({ |
33 | 34 | providedIn: 'root' |
... | ... | @@ -35,6 +36,7 @@ import { UtilsService } from '@core/services/utils.service'; |
35 | 36 | export class AuthGuard implements CanActivate, CanActivateChild { |
36 | 37 | |
37 | 38 | constructor(private store: Store<AppState>, |
39 | + private router: Router, | |
38 | 40 | private authService: AuthService, |
39 | 41 | private dialogService: DialogService, |
40 | 42 | private utils: UtilsService, |
... | ... | @@ -115,6 +117,14 @@ export class AuthGuard implements CanActivate, CanActivateChild { |
115 | 117 | if (data.auth && data.auth.indexOf(authority) === -1) { |
116 | 118 | this.dialogService.forbidden(); |
117 | 119 | return of(false); |
120 | + } else if (data.redirectTo) { | |
121 | + let redirect; | |
122 | + if (isObject(data.redirectTo)) { | |
123 | + redirect = data.redirectTo[authority]; | |
124 | + } else { | |
125 | + redirect = data.redirectTo; | |
126 | + } | |
127 | + return of(this.router.parseUrl(redirect)); | |
118 | 128 | } else { |
119 | 129 | return of(true); |
120 | 130 | } | ... | ... |
... | ... | @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; |
20 | 20 | import { HttpClient } from '@angular/common/http'; |
21 | 21 | import { PageLink } from '@shared/models/page/page-link'; |
22 | 22 | import { PageData } from '@shared/models/page/page-data'; |
23 | -import { Dashboard, DashboardInfo } from '@shared/models/dashboard.models'; | |
23 | +import { Dashboard, DashboardInfo, HomeDashboard, HomeDashboardInfo } from '@shared/models/dashboard.models'; | |
24 | 24 | import { WINDOW } from '@core/services/window.service'; |
25 | 25 | import { NavigationEnd, Router } from '@angular/router'; |
26 | 26 | import { filter, map, publishReplay, refCount } from 'rxjs/operators'; |
... | ... | @@ -122,6 +122,19 @@ export class DashboardService { |
122 | 122 | defaultHttpOptionsFromConfig(config)); |
123 | 123 | } |
124 | 124 | |
125 | + public getHomeDashboard(config?: RequestConfig): Observable<HomeDashboard> { | |
126 | + return this.http.get<HomeDashboard>('/api/dashboard/home', defaultHttpOptionsFromConfig(config)); | |
127 | + } | |
128 | + | |
129 | + public getTenantHomeDashboardInfo(config?: RequestConfig): Observable<HomeDashboardInfo> { | |
130 | + return this.http.get<HomeDashboardInfo>('/api/tenant/dashboard/home/info', defaultHttpOptionsFromConfig(config)); | |
131 | + } | |
132 | + | |
133 | + public setTenantHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo, config?: RequestConfig): Observable<any> { | |
134 | + return this.http.post<any>('/api/tenant/dashboard/home/info', homeDashboardInfo, | |
135 | + defaultHttpOptionsFromConfig(config)); | |
136 | + } | |
137 | + | |
125 | 138 | public getPublicDashboardLink(dashboard: DashboardInfo): string | null { |
126 | 139 | if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { |
127 | 140 | const publicCustomers = dashboard.assignedCustomers | ... | ... |
... | ... | @@ -223,6 +223,7 @@ export class MenuService { |
223 | 223 | name: 'home.home', |
224 | 224 | type: 'link', |
225 | 225 | path: '/home', |
226 | + notExact: true, | |
226 | 227 | icon: 'home' |
227 | 228 | }, |
228 | 229 | { |
... | ... | @@ -284,6 +285,13 @@ export class MenuService { |
284 | 285 | }, |
285 | 286 | { |
286 | 287 | id: guid(), |
288 | + name: 'admin.home-settings', | |
289 | + type: 'link', | |
290 | + path: '/settings/home', | |
291 | + icon: 'settings_applications' | |
292 | + }, | |
293 | + { | |
294 | + id: guid(), | |
287 | 295 | name: 'audit-log.audit-logs', |
288 | 296 | type: 'link', |
289 | 297 | path: '/auditLogs', |
... | ... | @@ -402,6 +410,7 @@ export class MenuService { |
402 | 410 | name: 'home.home', |
403 | 411 | type: 'link', |
404 | 412 | path: '/home', |
413 | + notExact: true, | |
405 | 414 | icon: 'home' |
406 | 415 | }, |
407 | 416 | { | ... | ... |
... | ... | @@ -14,14 +14,23 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | |
17 | +import { | |
18 | + Component, | |
19 | + Injector, | |
20 | + Input, | |
21 | + OnDestroy, | |
22 | + OnInit, | |
23 | + StaticProvider, | |
24 | + ViewChild, | |
25 | + ViewContainerRef | |
26 | +} from '@angular/core'; | |
18 | 27 | import { TooltipPosition } from '@angular/material/tooltip'; |
19 | 28 | import { AliasInfo, IAliasController } from '@core/api/widget-api.models'; |
20 | 29 | import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
21 | 30 | import { TranslateService } from '@ngx-translate/core'; |
22 | 31 | import { Subscription } from 'rxjs'; |
23 | 32 | import { BreakpointObserver } from '@angular/cdk/layout'; |
24 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
33 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
25 | 34 | import { |
26 | 35 | ALIASES_ENTITY_SELECT_PANEL_DATA, |
27 | 36 | AliasesEntitySelectPanelComponent, |
... | ... | @@ -136,12 +145,12 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy { |
136 | 145 | overlayRef.attach(new ComponentPortal(AliasesEntitySelectPanelComponent, this.viewContainerRef, injector)); |
137 | 146 | } |
138 | 147 | |
139 | - private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): PortalInjector { | |
140 | - const injectionTokens = new WeakMap<any, any>([ | |
141 | - [ALIASES_ENTITY_SELECT_PANEL_DATA, data], | |
142 | - [OverlayRef, overlayRef] | |
143 | - ]); | |
144 | - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
148 | + private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): Injector { | |
149 | + const providers: StaticProvider[] = [ | |
150 | + {provide: ALIASES_ENTITY_SELECT_PANEL_DATA, useValue: data}, | |
151 | + {provide: OverlayRef, useValue: overlayRef} | |
152 | + ]; | |
153 | + return Injector.create({parent: this.viewContainerRef.injector, providers}); | |
145 | 154 | } |
146 | 155 | |
147 | 156 | private updateDisplayValue() { | ... | ... |
... | ... | @@ -195,7 +195,8 @@ |
195 | 195 | [length]="dataSource.total() | async" |
196 | 196 | [pageIndex]="pageLink.page" |
197 | 197 | [pageSize]="pageLink.pageSize" |
198 | - [pageSizeOptions]="[10, 20, 30]"></mat-paginator> | |
198 | + [pageSizeOptions]="[10, 20, 30]" | |
199 | + showFirstLastButtons></mat-paginator> | |
199 | 200 | <ngx-hm-carousel fxFlex *ngIf="mode === 'widget' && widgetsList.length > 0" |
200 | 201 | #carousel |
201 | 202 | [(ngModel)]="widgetsCarouselIndex" | ... | ... |
... | ... | @@ -19,9 +19,11 @@ import { |
19 | 19 | ChangeDetectionStrategy, |
20 | 20 | Component, |
21 | 21 | ElementRef, |
22 | + Injector, | |
22 | 23 | Input, |
23 | 24 | NgZone, |
24 | 25 | OnInit, |
26 | + StaticProvider, | |
25 | 27 | ViewChild, |
26 | 28 | ViewContainerRef |
27 | 29 | } from '@angular/core'; |
... | ... | @@ -62,7 +64,7 @@ import { |
62 | 64 | EditAttributeValuePanelComponent, |
63 | 65 | EditAttributeValuePanelData |
64 | 66 | } from './edit-attribute-value-panel.component'; |
65 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
67 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
66 | 68 | import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; |
67 | 69 | import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; |
68 | 70 | import { DataKey, Datasource, DatasourceType, Widget, widgetType } from '@shared/models/widget.models'; |
... | ... | @@ -319,13 +321,19 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI |
319 | 321 | overlayRef.backdropClick().subscribe(() => { |
320 | 322 | overlayRef.dispose(); |
321 | 323 | }); |
322 | - const injectionTokens = new WeakMap<any, any>([ | |
323 | - [EDIT_ATTRIBUTE_VALUE_PANEL_DATA, { | |
324 | - attributeValue: attribute.value | |
325 | - } as EditAttributeValuePanelData], | |
326 | - [OverlayRef, overlayRef] | |
327 | - ]); | |
328 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
324 | + const providers: StaticProvider[] = [ | |
325 | + { | |
326 | + provide: EDIT_ATTRIBUTE_VALUE_PANEL_DATA, | |
327 | + useValue: { | |
328 | + attributeValue: attribute.value | |
329 | + } as EditAttributeValuePanelData | |
330 | + }, | |
331 | + { | |
332 | + provide: OverlayRef, | |
333 | + useValue: overlayRef | |
334 | + } | |
335 | + ]; | |
336 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
329 | 337 | const componentRef = overlayRef.attach(new ComponentPortal(EditAttributeValuePanelComponent, |
330 | 338 | this.viewContainerRef, injector)); |
331 | 339 | componentRef.onDestroy(() => { | ... | ... |
... | ... | @@ -49,10 +49,11 @@ |
49 | 49 | <tb-states-component fxFlex.lt-md |
50 | 50 | [statesControllerId]="isEdit ? 'default' : dashboardConfiguration.settings.stateControllerId" |
51 | 51 | [dashboardCtrl]="this" |
52 | - [dashboardId]="dashboard.id ? dashboard.id.id : ''" | |
52 | + [dashboardId]="(!embedded && dashboard.id) ? dashboard.id.id : ''" | |
53 | 53 | [isMobile]="isMobile" |
54 | 54 | [state]="dashboardCtx.state" |
55 | 55 | [currentState]="currentState" |
56 | + [syncStateWithQueryParam]="syncStateWithQueryParam" | |
56 | 57 | [states]="dashboardConfiguration.states"> |
57 | 58 | </tb-states-component> |
58 | 59 | </div> |
... | ... | @@ -78,7 +79,7 @@ |
78 | 79 | (click)="isFullscreen = !isFullscreen"> |
79 | 80 | <mat-icon>{{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> |
80 | 81 | </button> |
81 | - <button [fxShow]="isEdit || displayExport()" mat-icon-button | |
82 | + <button [fxShow]="currentDashboardId && (isEdit || displayExport())" mat-icon-button | |
82 | 83 | matTooltip="{{'dashboard.export' | translate}}" |
83 | 84 | matTooltipPosition="below" |
84 | 85 | (click)="exportDashboard($event)"> |
... | ... | @@ -118,7 +119,7 @@ |
118 | 119 | (click)="openDashboardSettings($event)"> |
119 | 120 | <mat-icon>settings</mat-icon> |
120 | 121 | </button> |
121 | - <tb-dashboard-select [fxShow]="!isEdit && !widgetEditMode && displayDashboardsSelect()" | |
122 | + <tb-dashboard-select [fxShow]="!isEdit && !widgetEditMode && !embedded && displayDashboardsSelect()" | |
122 | 123 | [(ngModel)]="currentDashboardId" |
123 | 124 | (ngModelChange)="currentDashboardIdChanged(currentDashboardId)" |
124 | 125 | [customerId]="currentCustomerId" | ... | ... |
... | ... | @@ -124,6 +124,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC |
124 | 124 | hideToolbar: boolean; |
125 | 125 | |
126 | 126 | @Input() |
127 | + syncStateWithQueryParam = true; | |
128 | + | |
129 | + @Input() | |
127 | 130 | dashboard: Dashboard; |
128 | 131 | dashboardConfiguration: DashboardConfiguration; |
129 | 132 | |
... | ... | @@ -266,9 +269,12 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC |
266 | 269 | this.rxSubscriptions.push(this.route.data.subscribe( |
267 | 270 | (data) => { |
268 | 271 | if (this.embedded) { |
269 | - data.dashboard = this.dashboard; | |
272 | + data.dashboard = this.dashboardUtils.validateAndUpdateDashboard(this.dashboard); | |
273 | + data.currentDashboardId = this.dashboard.id ? this.dashboard.id.id : null; | |
270 | 274 | data.widgetEditMode = false; |
271 | 275 | data.singlePageMode = false; |
276 | + } else { | |
277 | + data.currentDashboardId = this.route.snapshot.params.dashboardId; | |
272 | 278 | } |
273 | 279 | this.init(data); |
274 | 280 | this.runChangeDetection(); |
... | ... | @@ -286,7 +292,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC |
286 | 292 | |
287 | 293 | this.reset(); |
288 | 294 | |
289 | - this.currentDashboardId = this.route.snapshot.params.dashboardId; | |
295 | + this.currentDashboardId = data.currentDashboardId; | |
290 | 296 | |
291 | 297 | if (this.route.snapshot.params.customerId) { |
292 | 298 | this.currentCustomerId = this.route.snapshot.params.customerId; | ... | ... |
... | ... | @@ -23,6 +23,7 @@ |
23 | 23 | [widgetLayouts]="{}" |
24 | 24 | [isEdit]="false" |
25 | 25 | [isMobile]="true" |
26 | + [disableWidgetInteraction]="true" | |
26 | 27 | [isEditActionEnabled]="false" |
27 | 28 | [isExportActionEnabled]="false" |
28 | 29 | [isRemoveActionEnabled]="false" |
... | ... | @@ -34,6 +35,7 @@ |
34 | 35 | [widgetLayouts]="{}" |
35 | 36 | [isEdit]="false" |
36 | 37 | [isMobile]="true" |
38 | + [disableWidgetInteraction]="true" | |
37 | 39 | [isEditActionEnabled]="false" |
38 | 40 | [isExportActionEnabled]="false" |
39 | 41 | [isRemoveActionEnabled]="false" |
... | ... | @@ -45,6 +47,7 @@ |
45 | 47 | [widgetLayouts]="{}" |
46 | 48 | [isEdit]="false" |
47 | 49 | [isMobile]="true" |
50 | + [disableWidgetInteraction]="true" | |
48 | 51 | [isEditActionEnabled]="false" |
49 | 52 | [isExportActionEnabled]="false" |
50 | 53 | [isRemoveActionEnabled]="false" |
... | ... | @@ -56,6 +59,7 @@ |
56 | 59 | [widgetLayouts]="{}" |
57 | 60 | [isEdit]="false" |
58 | 61 | [isMobile]="true" |
62 | + [disableWidgetInteraction]="true" | |
59 | 63 | [isEditActionEnabled]="false" |
60 | 64 | [isExportActionEnabled]="false" |
61 | 65 | [isRemoveActionEnabled]="false" |
... | ... | @@ -67,6 +71,7 @@ |
67 | 71 | [widgetLayouts]="{}" |
68 | 72 | [isEdit]="false" |
69 | 73 | [isMobile]="true" |
74 | + [disableWidgetInteraction]="true" | |
70 | 75 | [isEditActionEnabled]="false" |
71 | 76 | [isExportActionEnabled]="false" |
72 | 77 | [isRemoveActionEnabled]="false" | ... | ... |
... | ... | @@ -53,6 +53,7 @@ |
53 | 53 | [mobileRowHeight]="layoutCtx.gridSettings.mobileRowHeight" |
54 | 54 | [isMobile]="isMobile" |
55 | 55 | [isMobileDisabled]="widgetEditMode" |
56 | + [disableWidgetInteraction]="isEdit" | |
56 | 57 | [isEditActionEnabled]="isEdit" |
57 | 58 | [isExportActionEnabled]="isEdit && !widgetEditMode" |
58 | 59 | [isRemoveActionEnabled]="isEdit && !widgetEditMode" | ... | ... |
... | ... | @@ -88,6 +88,8 @@ export abstract class StateControllerComponent implements IStateControllerCompon |
88 | 88 | |
89 | 89 | currentState: string; |
90 | 90 | |
91 | + syncStateWithQueryParam: boolean; | |
92 | + | |
91 | 93 | private rxSubscriptions = new Array<Subscription>(); |
92 | 94 | |
93 | 95 | private inited = false; |
... | ... | @@ -99,18 +101,20 @@ export abstract class StateControllerComponent implements IStateControllerCompon |
99 | 101 | } |
100 | 102 | |
101 | 103 | ngOnInit(): void { |
102 | - this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { | |
103 | - const dashboardId = this.route.snapshot.params.dashboardId || ''; | |
104 | - if (this.dashboardId === dashboardId) { | |
105 | - const newState = this.decodeStateParam(paramMap.get('state')); | |
106 | - if (this.currentState !== newState) { | |
107 | - this.currentState = newState; | |
108 | - if (this.inited) { | |
109 | - this.onStateChanged(); | |
104 | + if (this.syncStateWithQueryParam) { | |
105 | + this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { | |
106 | + const dashboardId = this.route.snapshot.params.dashboardId || ''; | |
107 | + if (this.dashboardId === dashboardId) { | |
108 | + const newState = this.decodeStateParam(paramMap.get('state')); | |
109 | + if (this.currentState !== newState) { | |
110 | + this.currentState = newState; | |
111 | + if (this.inited) { | |
112 | + this.onStateChanged(); | |
113 | + } | |
110 | 114 | } |
111 | 115 | } |
112 | - } | |
113 | - })); | |
116 | + })); | |
117 | + } | |
114 | 118 | this.init(); |
115 | 119 | this.inited = true; |
116 | 120 | } |
... | ... | @@ -124,16 +128,18 @@ export abstract class StateControllerComponent implements IStateControllerCompon |
124 | 128 | |
125 | 129 | protected updateStateParam(newState: string) { |
126 | 130 | this.currentState = newState; |
127 | - const queryParams: Params = { state: this.currentState }; | |
128 | - this.ngZone.run(() => { | |
129 | - this.router.navigate( | |
130 | - [], | |
131 | - { | |
132 | - relativeTo: this.route, | |
133 | - queryParams, | |
134 | - queryParamsHandling: 'merge', | |
135 | - }); | |
136 | - }); | |
131 | + if (this.syncStateWithQueryParam) { | |
132 | + const queryParams: Params = {state: this.currentState}; | |
133 | + this.ngZone.run(() => { | |
134 | + this.router.navigate( | |
135 | + [], | |
136 | + { | |
137 | + relativeTo: this.route, | |
138 | + queryParams, | |
139 | + queryParamsHandling: 'merge', | |
140 | + }); | |
141 | + }); | |
142 | + } | |
137 | 143 | } |
138 | 144 | |
139 | 145 | public openRightLayout(): void { | ... | ... |
... | ... | @@ -24,6 +24,7 @@ export interface IStateControllerComponent extends IStateController { |
24 | 24 | stateControllerInstanceId: string; |
25 | 25 | state: string; |
26 | 26 | currentState: string; |
27 | + syncStateWithQueryParam: boolean; | |
27 | 28 | isMobile: boolean; |
28 | 29 | states: {[id: string]: DashboardState }; |
29 | 30 | dashboardId: string; | ... | ... |
... | ... | @@ -54,6 +54,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { |
54 | 54 | currentState: string; |
55 | 55 | |
56 | 56 | @Input() |
57 | + syncStateWithQueryParam: boolean; | |
58 | + | |
59 | + @Input() | |
57 | 60 | isMobile: boolean; |
58 | 61 | |
59 | 62 | stateControllerComponentRef: ComponentRef<IStateControllerComponent>; |
... | ... | @@ -89,6 +92,8 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { |
89 | 92 | this.stateControllerComponent.state = this.state; |
90 | 93 | } else if (propName === 'currentState') { |
91 | 94 | this.stateControllerComponent.currentState = this.currentState; |
95 | + } else if (propName === 'syncStateWithQueryParam') { | |
96 | + this.stateControllerComponent.syncStateWithQueryParam = this.syncStateWithQueryParam; | |
92 | 97 | } |
93 | 98 | } |
94 | 99 | } |
... | ... | @@ -119,6 +124,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { |
119 | 124 | this.stateControllerComponent.stateControllerInstanceId = stateControllerInstanceId; |
120 | 125 | this.stateControllerComponent.state = this.state; |
121 | 126 | this.stateControllerComponent.currentState = this.currentState; |
127 | + this.stateControllerComponent.syncStateWithQueryParam = this.syncStateWithQueryParam; | |
122 | 128 | this.stateControllerComponent.isMobile = this.isMobile; |
123 | 129 | this.stateControllerComponent.states = this.states; |
124 | 130 | this.stateControllerComponent.dashboardId = this.dashboardId; | ... | ... |
... | ... | @@ -150,7 +150,7 @@ |
150 | 150 | </button> |
151 | 151 | </div> |
152 | 152 | </div> |
153 | - <div fxFlex fxLayout="column" class="tb-widget-content"> | |
153 | + <div fxFlex fxLayout="column" class="tb-widget-content" [ngClass]="{'tb-no-interaction': disableWidgetInteraction}"> | |
154 | 154 | <tb-widget fxFlex |
155 | 155 | #widgetComponent |
156 | 156 | [dashboardWidget]="widget" | ... | ... |
... | ... | @@ -114,6 +114,9 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo |
114 | 114 | isRemoveActionEnabled: boolean; |
115 | 115 | |
116 | 116 | @Input() |
117 | + disableWidgetInteraction = false; | |
118 | + | |
119 | + @Input() | |
117 | 120 | dashboardStyle: {[klass: string]: any}; |
118 | 121 | |
119 | 122 | @Input() | ... | ... |
... | ... | @@ -238,7 +238,8 @@ |
238 | 238 | [length]="dataSource.total() | async" |
239 | 239 | [pageIndex]="pageLink.page" |
240 | 240 | [pageSize]="pageLink.pageSize" |
241 | - [pageSizeOptions]="pageSizeOptions"></mat-paginator> | |
241 | + [pageSizeOptions]="pageSizeOptions" | |
242 | + showFirstLastButtons></mat-paginator> | |
242 | 243 | </div> |
243 | 244 | </div> |
244 | 245 | </mat-drawer-content> | ... | ... |
... | ... | @@ -44,7 +44,7 @@ import { EntityAction } from '@home/models/entity/entity-component.models'; |
44 | 44 | import { Subscription } from 'rxjs'; |
45 | 45 | import { MatTab, MatTabGroup } from '@angular/material/tabs'; |
46 | 46 | import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; |
47 | -import { deepClone } from '@core/utils'; | |
47 | +import { deepClone, mergeDeep } from '@core/utils'; | |
48 | 48 | |
49 | 49 | @Component({ |
50 | 50 | selector: 'tb-entity-details-panel', |
... | ... | @@ -280,7 +280,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit |
280 | 280 | |
281 | 281 | saveEntity() { |
282 | 282 | if (this.detailsForm.valid) { |
283 | - const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()}; | |
283 | + const editingEntity = mergeDeep(this.editingEntity, this.entityComponent.entityFormValue()); | |
284 | 284 | this.entitiesTableConfig.saveEntity(editingEntity).subscribe( |
285 | 285 | (entity) => { |
286 | 286 | this.entity = entity; | ... | ... |
... | ... | @@ -14,7 +14,16 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | |
17 | +import { | |
18 | + Component, | |
19 | + Injector, | |
20 | + Input, | |
21 | + OnDestroy, | |
22 | + OnInit, | |
23 | + StaticProvider, | |
24 | + ViewChild, | |
25 | + ViewContainerRef | |
26 | +} from '@angular/core'; | |
18 | 27 | import { TooltipPosition } from '@angular/material/tooltip'; |
19 | 28 | import { IAliasController } from '@core/api/widget-api.models'; |
20 | 29 | import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
... | ... | @@ -28,7 +37,7 @@ import { |
28 | 37 | FiltersEditPanelComponent, |
29 | 38 | FiltersEditPanelData |
30 | 39 | } from '@home/components/filter/filters-edit-panel.component'; |
31 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
40 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
32 | 41 | import { UserFilterDialogComponent, UserFilterDialogData } from '@home/components/filter/user-filter-dialog.component'; |
33 | 42 | import { MatDialog } from '@angular/material/dialog'; |
34 | 43 | |
... | ... | @@ -153,12 +162,12 @@ export class FiltersEditComponent implements OnInit, OnDestroy { |
153 | 162 | } |
154 | 163 | } |
155 | 164 | |
156 | - private _createFiltersEditPanelInjector(overlayRef: OverlayRef, data: FiltersEditPanelData): PortalInjector { | |
157 | - const injectionTokens = new WeakMap<any, any>([ | |
158 | - [FILTER_EDIT_PANEL_DATA, data], | |
159 | - [OverlayRef, overlayRef] | |
160 | - ]); | |
161 | - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
165 | + private _createFiltersEditPanelInjector(overlayRef: OverlayRef, data: FiltersEditPanelData): Injector { | |
166 | + const providers: StaticProvider[] = [ | |
167 | + {provide: FILTER_EDIT_PANEL_DATA, useValue: data}, | |
168 | + {provide: OverlayRef, useValue: overlayRef} | |
169 | + ]; | |
170 | + return Injector.create({parent: this.viewContainerRef.injector, providers}); | |
162 | 171 | } |
163 | 172 | |
164 | 173 | private updateFiltersInfo() { | ... | ... |
... | ... | @@ -67,15 +67,13 @@ |
67 | 67 | </mat-form-field> |
68 | 68 | <section fxLayout="column" [formGroup]="actionTypeFormGroup" [ngSwitch]="widgetActionFormGroup.get('type').value"> |
69 | 69 | <ng-template [ngSwitchCase]="widgetActionType.openDashboard"> |
70 | - <div fxLayout="column"> | |
71 | - <div class="mat-caption tb-required" | |
72 | - style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>widget-action.target-dashboard</div> | |
73 | - <tb-dashboard-autocomplete | |
74 | - formControlName="targetDashboardId" | |
75 | - required | |
76 | - [selectFirstDashboard]="true" | |
77 | - ></tb-dashboard-autocomplete> | |
78 | - </div> | |
70 | + <div class="mat-caption tb-required" | |
71 | + style="padding-left: 3px; padding-bottom: 10px; color: rgba(0,0,0,0.57);" translate>widget-action.target-dashboard</div> | |
72 | + <tb-dashboard-autocomplete | |
73 | + formControlName="targetDashboardId" | |
74 | + required | |
75 | + [selectFirstDashboard]="true" | |
76 | + ></tb-dashboard-autocomplete> | |
79 | 77 | </ng-template> |
80 | 78 | <ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState || |
81 | 79 | widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState || |
... | ... | @@ -122,26 +120,24 @@ |
122 | 120 | widgetActionFormGroup.get('type').value === widgetActionType.updateDashboardState || |
123 | 121 | widgetActionFormGroup.get('type').value === widgetActionType.openDashboard ? |
124 | 122 | widgetActionFormGroup.get('type').value : ''"> |
125 | - <div fxFlex fxLayout="column"> | |
126 | - <mat-checkbox formControlName="setEntityId"> | |
127 | - {{ 'widget-action.set-entity-from-widget' | translate }} | |
128 | - </mat-checkbox> | |
129 | - <mat-form-field *ngIf="actionTypeFormGroup.get('setEntityId').value" | |
130 | - floatLabel="always" | |
131 | - class="mat-block"> | |
132 | - <mat-label translate>alias.state-entity-parameter-name</mat-label> | |
133 | - <input matInput | |
134 | - placeholder="{{ 'alias.default-entity-parameter-name' | translate }}" | |
135 | - formControlName="stateEntityParamName"> | |
136 | - </mat-form-field> | |
137 | - </div> | |
123 | + <mat-checkbox formControlName="setEntityId"> | |
124 | + {{ 'widget-action.set-entity-from-widget' | translate }} | |
125 | + </mat-checkbox> | |
126 | + <mat-form-field *ngIf="actionTypeFormGroup.get('setEntityId').value" | |
127 | + floatLabel="always" | |
128 | + class="mat-block"> | |
129 | + <mat-label translate>alias.state-entity-parameter-name</mat-label> | |
130 | + <input matInput | |
131 | + placeholder="{{ 'alias.default-entity-parameter-name' | translate }}" | |
132 | + formControlName="stateEntityParamName"> | |
133 | + </mat-form-field> | |
138 | 134 | </ng-template> |
139 | 135 | <ng-template [ngSwitchCase]="widgetActionFormGroup.get('type').value === widgetActionType.openDashboardState ? |
140 | 136 | widgetActionFormGroup.get('type').value : ''"> |
141 | 137 | <mat-checkbox formControlName="openInSeparateDialog"> |
142 | 138 | {{ 'widget-action.open-in-separate-dialog' | translate }} |
143 | 139 | </mat-checkbox> |
144 | - <section fxLayout="column" *ngIf="actionTypeFormGroup.get('openInSeparateDialog').value"> | |
140 | + <section *ngIf="actionTypeFormGroup.get('openInSeparateDialog').value"> | |
145 | 141 | <mat-form-field class="mat-block"> |
146 | 142 | <mat-label translate>widget-action.dialog-title</mat-label> |
147 | 143 | <input matInput formControlName="dialogTitle"> | ... | ... |
... | ... | @@ -27,7 +27,8 @@ |
27 | 27 | </mat-progress-bar> |
28 | 28 | <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
29 | 29 | <div class="dashboard-state-dialog-content" mat-dialog-content fxFlex fxLayout="column" style="padding: 8px;"> |
30 | - <tb-dashboard-page [embedded]="true" [hideToolbar]="hideToolbar" [currentState]="state" [dashboard]="dashboard" style="width: 100%; height: 100%;"></tb-dashboard-page> | |
30 | + <tb-dashboard-page [embedded]="true" [syncStateWithQueryParam]="false" [hideToolbar]="hideToolbar" | |
31 | + [currentState]="state" [dashboard]="dashboard" style="width: 100%; height: 100%;"></tb-dashboard-page> | |
31 | 32 | </div> |
32 | 33 | <div mat-dialog-actions fxLayoutAlign="end center"> |
33 | 34 | <button mat-button color="primary" | ... | ... |
... | ... | @@ -14,11 +14,22 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, forwardRef, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | |
17 | +import { | |
18 | + Component, | |
19 | + forwardRef, | |
20 | + Inject, | |
21 | + Injector, | |
22 | + Input, | |
23 | + OnDestroy, | |
24 | + OnInit, | |
25 | + StaticProvider, | |
26 | + ViewChild, | |
27 | + ViewContainerRef | |
28 | +} from '@angular/core'; | |
18 | 29 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | 30 | import { DOCUMENT } from '@angular/common'; |
20 | 31 | import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
21 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
32 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
22 | 33 | import { MediaBreakpoints } from '@shared/models/constants'; |
23 | 34 | import { BreakpointObserver } from '@angular/cdk/layout'; |
24 | 35 | import { WINDOW } from '@core/services/window.service'; |
... | ... | @@ -140,12 +151,12 @@ export class LegendConfigComponent implements OnInit, OnDestroy, ControlValueAcc |
140 | 151 | overlayRef.attach(new ComponentPortal(LegendConfigPanelComponent, this.viewContainerRef, injector)); |
141 | 152 | } |
142 | 153 | |
143 | - private _createLegendConfigPanelInjector(overlayRef: OverlayRef, data: LegendConfigPanelData): PortalInjector { | |
144 | - const injectionTokens = new WeakMap<any, any>([ | |
145 | - [LEGEND_CONFIG_PANEL_DATA, data], | |
146 | - [OverlayRef, overlayRef] | |
147 | - ]); | |
148 | - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
154 | + private _createLegendConfigPanelInjector(overlayRef: OverlayRef, data: LegendConfigPanelData): Injector { | |
155 | + const providers: StaticProvider[] = [ | |
156 | + {provide: LEGEND_CONFIG_PANEL_DATA, useValue: data}, | |
157 | + {provide: OverlayRef, useValue: overlayRef} | |
158 | + ]; | |
159 | + return Injector.create({parent: this.viewContainerRef.injector, providers}); | |
149 | 160 | } |
150 | 161 | |
151 | 162 | registerOnChange(fn: any): void { | ... | ... |
... | ... | @@ -140,6 +140,7 @@ |
140 | 140 | [length]="alarmsDatasource.total() | async" |
141 | 141 | [pageIndex]="pageLink.page" |
142 | 142 | [pageSize]="pageLink.pageSize" |
143 | - [pageSizeOptions]="pageSizeOptions"></mat-paginator> | |
143 | + [pageSizeOptions]="pageSizeOptions" | |
144 | + showFirstLastButtons></mat-paginator> | |
144 | 145 | </div> |
145 | 146 | </div> | ... | ... |
... | ... | @@ -19,9 +19,11 @@ import { |
19 | 19 | Component, |
20 | 20 | ElementRef, |
21 | 21 | EventEmitter, |
22 | + Injector, | |
22 | 23 | Input, |
23 | 24 | NgZone, |
24 | 25 | OnInit, |
26 | + StaticProvider, | |
25 | 27 | ViewChild, |
26 | 28 | ViewContainerRef |
27 | 29 | } from '@angular/core'; |
... | ... | @@ -64,7 +66,7 @@ import { |
64 | 66 | widthStyle |
65 | 67 | } from '@home/components/widget/lib/table-widget.models'; |
66 | 68 | import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
67 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
69 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
68 | 70 | import { |
69 | 71 | DISPLAY_COLUMNS_PANEL_DATA, |
70 | 72 | DisplayColumnsPanelComponent, |
... | ... | @@ -452,20 +454,26 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, |
452 | 454 | }; |
453 | 455 | }); |
454 | 456 | |
455 | - const injectionTokens = new WeakMap<any, any>([ | |
456 | - [DISPLAY_COLUMNS_PANEL_DATA, { | |
457 | - columns, | |
458 | - columnsUpdated: (newColumns) => { | |
459 | - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); | |
460 | - if (this.enableSelection) { | |
461 | - this.displayedColumns.unshift('select'); | |
457 | + const providers: StaticProvider[] = [ | |
458 | + { | |
459 | + provide: DISPLAY_COLUMNS_PANEL_DATA, | |
460 | + useValue: { | |
461 | + columns, | |
462 | + columnsUpdated: (newColumns) => { | |
463 | + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); | |
464 | + if (this.enableSelection) { | |
465 | + this.displayedColumns.unshift('select'); | |
466 | + } | |
467 | + this.displayedColumns.push('actions'); | |
462 | 468 | } |
463 | - this.displayedColumns.push('actions'); | |
464 | - } | |
465 | - } as DisplayColumnsPanelData], | |
466 | - [OverlayRef, overlayRef] | |
467 | - ]); | |
468 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
469 | + } as DisplayColumnsPanelData | |
470 | + }, | |
471 | + { | |
472 | + provide: OverlayRef, | |
473 | + useValue: overlayRef | |
474 | + } | |
475 | + ]; | |
476 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
469 | 477 | overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, |
470 | 478 | this.viewContainerRef, injector)); |
471 | 479 | this.ctx.detectChanges(); |
... | ... | @@ -492,15 +500,21 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, |
492 | 500 | overlayRef.backdropClick().subscribe(() => { |
493 | 501 | overlayRef.dispose(); |
494 | 502 | }); |
495 | - const injectionTokens = new WeakMap<any, any>([ | |
496 | - [ALARM_FILTER_PANEL_DATA, { | |
497 | - statusList: this.pageLink.statusList, | |
498 | - severityList: this.pageLink.severityList, | |
499 | - typeList: this.pageLink.typeList | |
500 | - } as AlarmFilterPanelData], | |
501 | - [OverlayRef, overlayRef] | |
502 | - ]); | |
503 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
503 | + const providers: StaticProvider[] = [ | |
504 | + { | |
505 | + provide: ALARM_FILTER_PANEL_DATA, | |
506 | + useValue: { | |
507 | + statusList: this.pageLink.statusList, | |
508 | + severityList: this.pageLink.severityList, | |
509 | + typeList: this.pageLink.typeList | |
510 | + } as AlarmFilterPanelData | |
511 | + }, | |
512 | + { | |
513 | + provide: OverlayRef, | |
514 | + useValue: overlayRef | |
515 | + } | |
516 | + ]; | |
517 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
504 | 518 | const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterPanelComponent, |
505 | 519 | this.viewContainerRef, injector)); |
506 | 520 | componentRef.onDestroy(() => { | ... | ... |
... | ... | @@ -18,9 +18,11 @@ import { |
18 | 18 | Component, |
19 | 19 | Inject, |
20 | 20 | InjectionToken, |
21 | + Injector, | |
21 | 22 | Input, |
22 | 23 | OnDestroy, |
23 | 24 | OnInit, |
25 | + StaticProvider, | |
24 | 26 | ViewChild, |
25 | 27 | ViewContainerRef, |
26 | 28 | ViewEncapsulation |
... | ... | @@ -41,7 +43,7 @@ import { |
41 | 43 | import { KeyValue } from '@angular/common'; |
42 | 44 | import * as _moment from 'moment'; |
43 | 45 | import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
44 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
46 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
45 | 47 | import { MatSelect } from '@angular/material/select'; |
46 | 48 | import { Subscription } from 'rxjs'; |
47 | 49 | import { HistoryWindowType, TimewindowType } from '@shared/models/time/time.models'; |
... | ... | @@ -142,18 +144,24 @@ export class DateRangeNavigatorWidgetComponent extends PageComponent implements |
142 | 144 | overlayRef.backdropClick().subscribe(() => { |
143 | 145 | overlayRef.dispose(); |
144 | 146 | }); |
145 | - const injectionTokens = new WeakMap<any, any>([ | |
146 | - [DATE_RANGE_NAVIGATOR_PANEL_DATA, { | |
147 | - model: cloneDateRangeNavigatorModel(this.advancedModel), | |
148 | - settings: this.settings, | |
149 | - onChange: model => { | |
150 | - this.advancedModel = model; | |
151 | - this.triggerChange(); | |
152 | - } | |
153 | - } as DateRangeNavigatorPanelData], | |
154 | - [OverlayRef, overlayRef] | |
155 | - ]); | |
156 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
147 | + const providers: StaticProvider[] = [ | |
148 | + { | |
149 | + provide: DATE_RANGE_NAVIGATOR_PANEL_DATA, | |
150 | + useValue: { | |
151 | + model: cloneDateRangeNavigatorModel(this.advancedModel), | |
152 | + settings: this.settings, | |
153 | + onChange: model => { | |
154 | + this.advancedModel = model; | |
155 | + this.triggerChange(); | |
156 | + } | |
157 | + } as DateRangeNavigatorPanelData | |
158 | + }, | |
159 | + { | |
160 | + provide: OverlayRef, | |
161 | + useValue: overlayRef | |
162 | + } | |
163 | + ]; | |
164 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
157 | 165 | overlayRef.attach(new ComponentPortal(DateRangeNavigatorPanelComponent, |
158 | 166 | this.viewContainerRef, injector)); |
159 | 167 | this.ctx.detectChanges(); | ... | ... |
... | ... | @@ -99,6 +99,7 @@ |
99 | 99 | [length]="entityDatasource.total() | async" |
100 | 100 | [pageIndex]="pageLink.page" |
101 | 101 | [pageSize]="pageLink.pageSize" |
102 | - [pageSizeOptions]="pageSizeOptions"></mat-paginator> | |
102 | + [pageSizeOptions]="pageSizeOptions" | |
103 | + showFirstLastButtons></mat-paginator> | |
103 | 104 | </div> |
104 | 105 | </div> | ... | ... |
... | ... | @@ -18,9 +18,11 @@ import { |
18 | 18 | AfterViewInit, |
19 | 19 | Component, |
20 | 20 | ElementRef, |
21 | + Injector, | |
21 | 22 | Input, |
22 | 23 | NgZone, |
23 | 24 | OnInit, |
25 | + StaticProvider, | |
24 | 26 | ViewChild, |
25 | 27 | ViewContainerRef |
26 | 28 | } from '@angular/core'; |
... | ... | @@ -70,7 +72,7 @@ import { |
70 | 72 | widthStyle |
71 | 73 | } from '@home/components/widget/lib/table-widget.models'; |
72 | 74 | import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
73 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
75 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
74 | 76 | import { |
75 | 77 | DISPLAY_COLUMNS_PANEL_DATA, |
76 | 78 | DisplayColumnsPanelComponent, |
... | ... | @@ -422,17 +424,23 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni |
422 | 424 | }; |
423 | 425 | }); |
424 | 426 | |
425 | - const injectionTokens = new WeakMap<any, any>([ | |
426 | - [DISPLAY_COLUMNS_PANEL_DATA, { | |
427 | - columns, | |
428 | - columnsUpdated: (newColumns) => { | |
429 | - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); | |
430 | - this.displayedColumns.push('actions'); | |
431 | - } | |
432 | - } as DisplayColumnsPanelData], | |
433 | - [OverlayRef, overlayRef] | |
434 | - ]); | |
435 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
427 | + const providers: StaticProvider[] = [ | |
428 | + { | |
429 | + provide: DISPLAY_COLUMNS_PANEL_DATA, | |
430 | + useValue: { | |
431 | + columns, | |
432 | + columnsUpdated: (newColumns) => { | |
433 | + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); | |
434 | + this.displayedColumns.push('actions'); | |
435 | + } | |
436 | + } as DisplayColumnsPanelData | |
437 | + }, | |
438 | + { | |
439 | + provide: OverlayRef, | |
440 | + useValue: overlayRef | |
441 | + } | |
442 | + ]; | |
443 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
436 | 444 | overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, |
437 | 445 | this.viewContainerRef, injector)); |
438 | 446 | this.ctx.detectChanges(); | ... | ... |
... | ... | @@ -175,7 +175,7 @@ export class TbFlot { |
175 | 175 | autoHighlight: this.tooltipIndividual === true, |
176 | 176 | markings: [] |
177 | 177 | }, |
178 | - selection : { mode : ctx.isMobile ? null : 'x' }, | |
178 | + selection : { mode : 'x' }, | |
179 | 179 | legend : { |
180 | 180 | show: false |
181 | 181 | } |
... | ... | @@ -702,7 +702,7 @@ export class TbFlot { |
702 | 702 | } |
703 | 703 | |
704 | 704 | public checkMouseEvents() { |
705 | - const enabled = !this.ctx.isMobile && !this.ctx.isEdit; | |
705 | + const enabled = !this.ctx.isEdit; | |
706 | 706 | if (isUndefined(this.mouseEventsEnabled) || this.mouseEventsEnabled !== enabled) { |
707 | 707 | this.mouseEventsEnabled = enabled; |
708 | 708 | if (this.$element) { | ... | ... |
... | ... | @@ -626,10 +626,14 @@ export default abstract class LeafletMap { |
626 | 626 | } |
627 | 627 | this.points = new FeatureGroup(); |
628 | 628 | } |
629 | + let pointColor = this.options.pointColor; | |
629 | 630 | for (const pointsList of pointsData) { |
630 | 631 | pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { |
632 | + if (this.options.useColorPointFunction) { | |
633 | + pointColor = safeExecute(this.options.colorPointFunction, [data, pointsData, data.dsIndex]); | |
634 | + } | |
631 | 635 | const point = L.circleMarker(this.convertPosition(data), { |
632 | - color: this.options.pointColor, | |
636 | + color: pointColor, | |
633 | 637 | radius: this.options.pointSize |
634 | 638 | }); |
635 | 639 | if (!this.options.pointTooltipOnRightPanel) { | ... | ... |
... | ... | @@ -201,6 +201,8 @@ export type TripAnimationSettings = { |
201 | 201 | pointAsAnchorFunction: GenericFunction; |
202 | 202 | tooltipFunction: GenericFunction; |
203 | 203 | labelFunction: GenericFunction; |
204 | + useColorPointFunction: boolean; | |
205 | + colorPointFunction: GenericFunction; | |
204 | 206 | }; |
205 | 207 | |
206 | 208 | export type actionsHandler = ($event: Event, datasource: Datasource) => void; | ... | ... |
... | ... | @@ -301,6 +301,7 @@ export class MapWidgetController implements MapWidgetInterface { |
301 | 301 | labelFunction: parseFunction(settings.labelFunction, functionParams), |
302 | 302 | tooltipFunction: parseFunction(settings.tooltipFunction, functionParams), |
303 | 303 | colorFunction: parseFunction(settings.colorFunction, functionParams), |
304 | + colorPointFunction: parseFunction(settings.colorPointFunction, functionParams), | |
304 | 305 | polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams), |
305 | 306 | polygonTooltipFunction: parseFunction(settings.polygonTooltipFunction, functionParams), |
306 | 307 | markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']), | ... | ... |
... | ... | @@ -871,6 +871,15 @@ export const pointSchema = |
871 | 871 | title: 'Point color', |
872 | 872 | type: 'string' |
873 | 873 | }, |
874 | + useColorPointFunction: { | |
875 | + title: 'Use color point function', | |
876 | + type: 'boolean', | |
877 | + default: false | |
878 | + }, | |
879 | + colorPointFunction: { | |
880 | + title: 'Color point function: f(data, dsData, dsIndex)', | |
881 | + type: 'string' | |
882 | + }, | |
874 | 883 | pointSize: { |
875 | 884 | title: 'Point size (px)', |
876 | 885 | type: 'number', |
... | ... | @@ -899,6 +908,11 @@ export const pointSchema = |
899 | 908 | key: 'pointColor', |
900 | 909 | type: 'color' |
901 | 910 | }, |
911 | + 'useColorPointFunction', | |
912 | + { | |
913 | + key: 'colorPointFunction', | |
914 | + type: 'javascript' | |
915 | + }, | |
902 | 916 | 'pointSize', |
903 | 917 | 'usePointAsAnchor', |
904 | 918 | { | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2021 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<a mat-raised-button color="primary" class="tb-nav-button" href="{{settings.path}}" (click)="navigate($event, settings.path)"> | |
19 | + <mat-icon class="material-icons tb-mat-96">{{settings.icon}}</mat-icon> | |
20 | + <span>{{translatedName}}</span> | |
21 | +</a> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +:host { | |
18 | + width: 100%; | |
19 | + height: 100%; | |
20 | +} | |
21 | + | |
22 | +:host ::ng-deep { | |
23 | + .tb-nav-button { | |
24 | + width: 100%; | |
25 | + height: 100%; | |
26 | + &:hover { | |
27 | + border-bottom: none; | |
28 | + } | |
29 | + &:focus { | |
30 | + border-bottom: none; | |
31 | + } | |
32 | + .mat-button-wrapper { | |
33 | + width: 100%; | |
34 | + height: 100%; | |
35 | + display: flex; | |
36 | + flex-direction: column; | |
37 | + align-items: center; | |
38 | + mat-icon { | |
39 | + margin: auto; | |
40 | + } | |
41 | + span { | |
42 | + height: 18px; | |
43 | + min-height: 36px; | |
44 | + max-height: 36px; | |
45 | + padding: 0 0 20px 0; | |
46 | + margin: auto; | |
47 | + font-size: 18px; | |
48 | + font-weight: 400; | |
49 | + line-height: 18px; | |
50 | + white-space: normal; | |
51 | + } | |
52 | + } | |
53 | + &.mat-raised-button.mat-primary { | |
54 | + .mat-ripple-element { | |
55 | + opacity: 0.3; | |
56 | + background-color: rgba(255, 255, 255, 0.3); | |
57 | + } | |
58 | + } | |
59 | + } | |
60 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { PageComponent } from '@shared/components/page.component'; | |
18 | +import { Component, Input, NgZone, OnInit } from '@angular/core'; | |
19 | +import { WidgetContext } from '@home/models/widget-component.models'; | |
20 | +import { Store } from '@ngrx/store'; | |
21 | +import { AppState } from '@core/core.state'; | |
22 | +import { Router } from '@angular/router'; | |
23 | +import { UtilsService } from '@core/services/utils.service'; | |
24 | + | |
25 | +interface NavigationCardWidgetSettings { | |
26 | + name: string; | |
27 | + icon: string; | |
28 | + path: string; | |
29 | +} | |
30 | + | |
31 | +@Component({ | |
32 | + selector: 'tb-navigation-card-widget', | |
33 | + templateUrl: './navigation-card-widget.component.html', | |
34 | + styleUrls: ['./navigation-card-widget.component.scss'] | |
35 | +}) | |
36 | +export class NavigationCardWidgetComponent extends PageComponent implements OnInit { | |
37 | + | |
38 | + settings: NavigationCardWidgetSettings; | |
39 | + | |
40 | + translatedName: string; | |
41 | + | |
42 | + @Input() | |
43 | + ctx: WidgetContext; | |
44 | + | |
45 | + constructor(protected store: Store<AppState>, | |
46 | + private utils: UtilsService, | |
47 | + private ngZone: NgZone, | |
48 | + private router: Router) { | |
49 | + super(store); | |
50 | + } | |
51 | + | |
52 | + ngOnInit(): void { | |
53 | + this.ctx.$scope.navigationCardWidget = this; | |
54 | + this.settings = this.ctx.settings; | |
55 | + this.translatedName = this.utils.customTranslation(this.settings.name, this.settings.name); | |
56 | + } | |
57 | + | |
58 | + | |
59 | + navigate($event: Event, path: string) { | |
60 | + $event.preventDefault(); | |
61 | + this.ngZone.run(() => { | |
62 | + this.router.navigateByUrl(path); | |
63 | + }); | |
64 | + } | |
65 | + | |
66 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2021 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<mat-grid-list *ngIf="cols" class="tb-navigation-cards" [cols]="cols" rowHeight="280px"> | |
19 | + <mat-grid-tile [colspan]="sectionColspan(section)" *ngFor="let section of showHomeSections$| async"> | |
20 | + <mat-card style="width: 100%;"> | |
21 | + <mat-card-title> | |
22 | + <span translate class="mat-headline">{{section.name}}</span> | |
23 | + </mat-card-title> | |
24 | + <mat-card-content> | |
25 | + <mat-grid-list rowHeight="170px" [cols]="sectionPlaces(section).length"> | |
26 | + <mat-grid-tile *ngFor="let place of sectionPlaces(section)"> | |
27 | + <a mat-raised-button color="primary" class="tb-card-button" href="{{place.path}}" (click)="navigate($event, place.path)"> | |
28 | + <mat-icon *ngIf="!place.isMdiIcon" class="material-icons tb-mat-96">{{place.icon}}</mat-icon> | |
29 | + <mat-icon *ngIf="place.isMdiIcon" class="tb-mat-96" [svgIcon]="place.icon"></mat-icon> | |
30 | + <span translate>{{place.name}}</span> | |
31 | + </a> | |
32 | + </mat-grid-tile> | |
33 | + </mat-grid-list> | |
34 | + </mat-card-content> | |
35 | + </mat-card> | |
36 | + </mat-grid-tile> | |
37 | +</mat-grid-list> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +@import '../../../../../../scss/constants'; | |
17 | + | |
18 | +:host { | |
19 | + width: 100%; | |
20 | + height: 100%; | |
21 | +} | |
22 | + | |
23 | +:host ::ng-deep { | |
24 | + .tb-navigation-cards { | |
25 | + .mat-headline { | |
26 | + font-size: 20px; | |
27 | + @media #{$mat-gt-xmd} { | |
28 | + font-size: 24px; | |
29 | + } | |
30 | + } | |
31 | + mat-card { | |
32 | + padding: 0; | |
33 | + margin: 8px; | |
34 | + mat-card-title { | |
35 | + margin: 0; | |
36 | + padding: 24px 16px 16px; | |
37 | + } | |
38 | + mat-card-title+mat-card-content { | |
39 | + padding-top: 0; | |
40 | + } | |
41 | + mat-card-content { | |
42 | + padding: 16px; | |
43 | + } | |
44 | + } | |
45 | + .tb-card-button { | |
46 | + width: 100%; | |
47 | + height: 100%; | |
48 | + max-width: 240px; | |
49 | + &:hover { | |
50 | + border-bottom: none; | |
51 | + } | |
52 | + &:focus { | |
53 | + border-bottom: none; | |
54 | + } | |
55 | + .mat-button-wrapper { | |
56 | + width: 100%; | |
57 | + height: 100%; | |
58 | + display: flex; | |
59 | + flex-direction: column; | |
60 | + align-items: center; | |
61 | + mat-icon { | |
62 | + margin: auto; | |
63 | + } | |
64 | + span { | |
65 | + height: 18px; | |
66 | + min-height: 36px; | |
67 | + max-height: 36px; | |
68 | + padding: 0 0 20px 0; | |
69 | + margin: auto; | |
70 | + font-size: 18px; | |
71 | + font-weight: 400; | |
72 | + line-height: 18px; | |
73 | + white-space: normal; | |
74 | + } | |
75 | + } | |
76 | + &.mat-raised-button.mat-primary { | |
77 | + .mat-ripple-element { | |
78 | + opacity: 0.3; | |
79 | + background-color: rgba(255, 255, 255, 0.3); | |
80 | + } | |
81 | + } | |
82 | + } | |
83 | + } | |
84 | +} | |
85 | + | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { PageComponent } from '@shared/components/page.component'; | |
18 | +import { Component, Input, NgZone, OnInit } from '@angular/core'; | |
19 | +import { WidgetContext } from '@home/models/widget-component.models'; | |
20 | +import { Store } from '@ngrx/store'; | |
21 | +import { AppState } from '@core/core.state'; | |
22 | +import { MenuService } from '@core/services/menu.service'; | |
23 | +import { HomeSection, HomeSectionPlace } from '@core/services/menu.models'; | |
24 | +import { Router } from '@angular/router'; | |
25 | +import { map } from 'rxjs/operators'; | |
26 | + | |
27 | +interface NavigationCardsWidgetSettings { | |
28 | + filterType: 'all' | 'include' | 'exclude'; | |
29 | + filter: string[]; | |
30 | +} | |
31 | + | |
32 | +@Component({ | |
33 | + selector: 'tb-navigation-cards-widget', | |
34 | + templateUrl: './navigation-cards-widget.component.html', | |
35 | + styleUrls: ['./navigation-cards-widget.component.scss'] | |
36 | +}) | |
37 | +export class NavigationCardsWidgetComponent extends PageComponent implements OnInit { | |
38 | + | |
39 | + homeSections$ = this.menuService.homeSections(); | |
40 | + showHomeSections$ = this.homeSections$.pipe( | |
41 | + map((sections) => { | |
42 | + return sections.filter((section) => this.sectionPlaces(section).length > 0); | |
43 | + }) | |
44 | + ); | |
45 | + | |
46 | + cols = null; | |
47 | + | |
48 | + settings: NavigationCardsWidgetSettings; | |
49 | + | |
50 | + @Input() | |
51 | + ctx: WidgetContext; | |
52 | + | |
53 | + constructor(protected store: Store<AppState>, | |
54 | + private menuService: MenuService, | |
55 | + private ngZone: NgZone, | |
56 | + private router: Router) { | |
57 | + super(store); | |
58 | + } | |
59 | + | |
60 | + ngOnInit(): void { | |
61 | + this.ctx.$scope.navigationCardsWidget = this; | |
62 | + this.settings = this.ctx.settings; | |
63 | + } | |
64 | + | |
65 | + resize() { | |
66 | + this.updateColumnCount(); | |
67 | + } | |
68 | + | |
69 | + private updateColumnCount() { | |
70 | + this.cols = 2; | |
71 | + const width = this.ctx.width; | |
72 | + if (width >= 1280) { | |
73 | + this.cols = 3; | |
74 | + if (width >= 1920) { | |
75 | + this.cols = 4; | |
76 | + } | |
77 | + } | |
78 | + this.ctx.detectChanges(); | |
79 | + } | |
80 | + | |
81 | + navigate($event: Event, path: string) { | |
82 | + $event.preventDefault(); | |
83 | + this.ngZone.run(() => { | |
84 | + this.router.navigateByUrl(path); | |
85 | + }); | |
86 | + } | |
87 | + | |
88 | + sectionPlaces(section: HomeSection): HomeSectionPlace[] { | |
89 | + return section && section.places ? section.places.filter((place) => this.filterPlace(place)) : []; | |
90 | + } | |
91 | + | |
92 | + private filterPlace(place: HomeSectionPlace): boolean { | |
93 | + if (this.settings.filterType === 'include') { | |
94 | + return this.settings.filter.includes(place.path); | |
95 | + } else if (this.settings.filterType === 'exclude') { | |
96 | + return !this.settings.filter.includes(place.path); | |
97 | + } | |
98 | + return true; | |
99 | + } | |
100 | + | |
101 | + sectionColspan(section: HomeSection): number { | |
102 | + if (this.ctx.width >= 960) { | |
103 | + let colspan = this.cols; | |
104 | + const places = this.sectionPlaces(section); | |
105 | + if (places.length <= colspan) { | |
106 | + colspan = places.length; | |
107 | + } | |
108 | + return colspan; | |
109 | + } else { | |
110 | + return 2; | |
111 | + } | |
112 | + } | |
113 | + | |
114 | +} | ... | ... |
... | ... | @@ -104,7 +104,8 @@ |
104 | 104 | [length]="source.timeseriesDatasource.total() | async" |
105 | 105 | [pageIndex]="source.pageLink.page" |
106 | 106 | [pageSize]="source.pageLink.pageSize" |
107 | - [pageSizeOptions]="pageSizeOptions"></mat-paginator> | |
107 | + [pageSizeOptions]="pageSizeOptions" | |
108 | + showFirstLastButtons></mat-paginator> | |
108 | 109 | </mat-tab> |
109 | 110 | </mat-tab-group> |
110 | 111 | </div> | ... | ... |
... | ... | @@ -210,7 +210,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI |
210 | 210 | this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; |
211 | 211 | this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false; |
212 | 212 | this.showTimestamp = this.settings.showTimestamp !== false; |
213 | - this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.sss'; | |
213 | + this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS'; | |
214 | 214 | |
215 | 215 | const pageSize = this.settings.defaultPageSize; |
216 | 216 | if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { | ... | ... |
... | ... | @@ -112,6 +112,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy |
112 | 112 | this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']); |
113 | 113 | this.settings.tooltipFunction = parseFunction(this.settings.tooltipFunction, ['data', 'dsData', 'dsIndex']); |
114 | 114 | this.settings.labelFunction = parseFunction(this.settings.labelFunction, ['data', 'dsData', 'dsIndex']); |
115 | + this.settings.colorPointFunction = parseFunction(this.settings.colorPointFunction, ['data', 'dsData', 'dsIndex']); | |
115 | 116 | this.normalizationStep = this.settings.normalizationStep; |
116 | 117 | const subscription = this.ctx.defaultSubscription; |
117 | 118 | subscription.callbacks.onDataUpdated = () => { | ... | ... |
... | ... | @@ -35,6 +35,8 @@ import { TripAnimationComponent } from './trip-animation/trip-animation.componen |
35 | 35 | import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; |
36 | 36 | import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; |
37 | 37 | import { ImportExportService } from '@home/components/import-export/import-export.service'; |
38 | +import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navigation-cards-widget.component'; | |
39 | +import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component'; | |
38 | 40 | |
39 | 41 | @NgModule({ |
40 | 42 | declarations: |
... | ... | @@ -50,7 +52,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor |
50 | 52 | MultipleInputWidgetComponent, |
51 | 53 | TripAnimationComponent, |
52 | 54 | PhotoCameraInputWidgetComponent, |
53 | - GatewayFormComponent | |
55 | + GatewayFormComponent, | |
56 | + NavigationCardsWidgetComponent, | |
57 | + NavigationCardWidgetComponent | |
54 | 58 | ], |
55 | 59 | imports: [ |
56 | 60 | CommonModule, |
... | ... | @@ -68,7 +72,9 @@ import { ImportExportService } from '@home/components/import-export/import-expor |
68 | 72 | MultipleInputWidgetComponent, |
69 | 73 | TripAnimationComponent, |
70 | 74 | PhotoCameraInputWidgetComponent, |
71 | - GatewayFormComponent | |
75 | + GatewayFormComponent, | |
76 | + NavigationCardsWidgetComponent, | |
77 | + NavigationCardWidgetComponent | |
72 | 78 | ], |
73 | 79 | providers: [ |
74 | 80 | CustomDialogService, | ... | ... |
... | ... | @@ -32,6 +32,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; |
32 | 32 | import { OAuth2Service } from '@core/http/oauth2.service'; |
33 | 33 | import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; |
34 | 34 | import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; |
35 | +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; | |
35 | 36 | |
36 | 37 | @Injectable() |
37 | 38 | export class OAuth2LoginProcessingUrlResolver implements Resolve<string> { |
... | ... | @@ -48,7 +49,7 @@ const routes: Routes = [ |
48 | 49 | { |
49 | 50 | path: 'settings', |
50 | 51 | data: { |
51 | - auth: [Authority.SYS_ADMIN], | |
52 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | |
52 | 53 | breadcrumb: { |
53 | 54 | label: 'admin.system-settings', |
54 | 55 | icon: 'settings' |
... | ... | @@ -57,8 +58,13 @@ const routes: Routes = [ |
57 | 58 | children: [ |
58 | 59 | { |
59 | 60 | path: '', |
60 | - redirectTo: 'general', | |
61 | - pathMatch: 'full' | |
61 | + data: { | |
62 | + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], | |
63 | + redirectTo: { | |
64 | + SYS_ADMIN: '/settings/general', | |
65 | + TENANT_ADMIN: '/settings/home' | |
66 | + } | |
67 | + } | |
62 | 68 | }, |
63 | 69 | { |
64 | 70 | path: 'general', |
... | ... | @@ -127,6 +133,19 @@ const routes: Routes = [ |
127 | 133 | resolve: { |
128 | 134 | loginProcessingUrl: OAuth2LoginProcessingUrlResolver |
129 | 135 | } |
136 | + }, | |
137 | + { | |
138 | + path: 'home', | |
139 | + component: HomeSettingsComponent, | |
140 | + canDeactivate: [ConfirmOnExitGuard], | |
141 | + data: { | |
142 | + auth: [Authority.TENANT_ADMIN], | |
143 | + title: 'admin.home-settings', | |
144 | + breadcrumb: { | |
145 | + label: 'admin.home-settings', | |
146 | + icon: 'settings_applications' | |
147 | + } | |
148 | + } | |
130 | 149 | } |
131 | 150 | ] |
132 | 151 | } | ... | ... |
... | ... | @@ -26,6 +26,7 @@ import { HomeComponentsModule } from '@modules/home/components/home-components.m |
26 | 26 | import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; |
27 | 27 | import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; |
28 | 28 | import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; |
29 | +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; | |
29 | 30 | |
30 | 31 | @NgModule({ |
31 | 32 | declarations: |
... | ... | @@ -35,7 +36,8 @@ import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dial |
35 | 36 | SmsProviderComponent, |
36 | 37 | SendTestSmsDialogComponent, |
37 | 38 | SecuritySettingsComponent, |
38 | - OAuth2SettingsComponent | |
39 | + OAuth2SettingsComponent, | |
40 | + HomeSettingsComponent | |
39 | 41 | ], |
40 | 42 | imports: [ |
41 | 43 | CommonModule, | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2021 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | +<div> | |
19 | + <mat-card class="settings-card"> | |
20 | + <mat-card-title> | |
21 | + <div fxLayout="row"> | |
22 | + <span class="mat-headline" translate>admin.home-settings</span> | |
23 | + </div> | |
24 | + </mat-card-title> | |
25 | + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> | |
26 | + </mat-progress-bar> | |
27 | + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> | |
28 | + <mat-card-content style="padding-top: 16px;"> | |
29 | + <form [formGroup]="homeSettings" (ngSubmit)="save()"> | |
30 | + <fieldset [disabled]="isLoading$ | async"> | |
31 | + <section class="tb-default-dashboard" fxFlex fxLayout="column"> | |
32 | + <section fxFlex fxLayout="column" fxLayout.gt-sm="row"> | |
33 | + <tb-dashboard-autocomplete | |
34 | + fxFlex | |
35 | + placeholder="{{ 'dashboard.home-dashboard' | translate }}" | |
36 | + formControlName="dashboardId" | |
37 | + [dashboardsScope]="'tenant'" | |
38 | + [selectFirstDashboard]="false" | |
39 | + ></tb-dashboard-autocomplete> | |
40 | + <mat-checkbox fxFlex formControlName="hideDashboardToolbar"> | |
41 | + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} | |
42 | + </mat-checkbox> | |
43 | + </section> | |
44 | + </section> | |
45 | + <div fxLayout="row" fxLayoutAlign="end center" style="width: 100%;" class="layout-wrap"> | |
46 | + <button mat-button mat-raised-button color="primary" [disabled]="(isLoading$ | async) || homeSettings.invalid || !homeSettings.dirty" | |
47 | + type="submit">{{'action.save' | translate}} | |
48 | + </button> | |
49 | + </div> | |
50 | + </fieldset> | |
51 | + </form> | |
52 | + </mat-card-content> | |
53 | + </mat-card> | |
54 | +</div> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +@import "../../../../../scss/constants"; | |
18 | + | |
19 | +:host { | |
20 | + .tb-default-dashboard { | |
21 | + tb-dashboard-autocomplete { | |
22 | + @media #{$mat-gt-sm} { | |
23 | + padding-right: 12px; | |
24 | + } | |
25 | + | |
26 | + @media #{$mat-lt-md} { | |
27 | + padding-bottom: 12px; | |
28 | + } | |
29 | + } | |
30 | + mat-checkbox { | |
31 | + @media #{$mat-gt-sm} { | |
32 | + margin-top: 16px; | |
33 | + } | |
34 | + } | |
35 | + } | |
36 | +} | ... | ... |
1 | +/// | |
2 | +/// Copyright © 2016-2021 The Thingsboard Authors | |
3 | +/// | |
4 | +/// Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | +/// you may not use this file except in compliance with the License. | |
6 | +/// You may obtain a copy of the License at | |
7 | +/// | |
8 | +/// http://www.apache.org/licenses/LICENSE-2.0 | |
9 | +/// | |
10 | +/// Unless required by applicable law or agreed to in writing, software | |
11 | +/// distributed under the License is distributed on an "AS IS" BASIS, | |
12 | +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | +/// See the License for the specific language governing permissions and | |
14 | +/// limitations under the License. | |
15 | +/// | |
16 | + | |
17 | +import { Component, OnInit } from '@angular/core'; | |
18 | +import { Store } from '@ngrx/store'; | |
19 | +import { AppState } from '@core/core.state'; | |
20 | +import { PageComponent } from '@shared/components/page.component'; | |
21 | +import { Router } from '@angular/router'; | |
22 | +import { FormBuilder, FormGroup } from '@angular/forms'; | |
23 | +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; | |
24 | +import { DashboardService } from '@core/http/dashboard.service'; | |
25 | +import { HomeDashboardInfo } from '@shared/models/dashboard.models'; | |
26 | +import { isDefinedAndNotNull } from '@core/utils'; | |
27 | +import { DashboardId } from '@shared/models/id/dashboard-id'; | |
28 | + | |
29 | +@Component({ | |
30 | + selector: 'tb-home-settings', | |
31 | + templateUrl: './home-settings.component.html', | |
32 | + styleUrls: ['./home-settings.component.scss', './settings-card.scss'] | |
33 | +}) | |
34 | +export class HomeSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { | |
35 | + | |
36 | + homeSettings: FormGroup; | |
37 | + | |
38 | + constructor(protected store: Store<AppState>, | |
39 | + private router: Router, | |
40 | + private dashboardService: DashboardService, | |
41 | + public fb: FormBuilder) { | |
42 | + super(store); | |
43 | + } | |
44 | + | |
45 | + ngOnInit() { | |
46 | + this.homeSettings = this.fb.group({ | |
47 | + dashboardId: [null], | |
48 | + hideDashboardToolbar: [true] | |
49 | + }); | |
50 | + this.dashboardService.getTenantHomeDashboardInfo().subscribe( | |
51 | + (homeDashboardInfo) => { | |
52 | + this.setHomeDashboardInfo(homeDashboardInfo); | |
53 | + } | |
54 | + ); | |
55 | + } | |
56 | + | |
57 | + save(): void { | |
58 | + const strDashboardId = this.homeSettings.get('dashboardId').value; | |
59 | + const dashboardId: DashboardId = strDashboardId ? new DashboardId(strDashboardId) : null; | |
60 | + const hideDashboardToolbar = this.homeSettings.get('hideDashboardToolbar').value; | |
61 | + const homeDashboardInfo: HomeDashboardInfo = { | |
62 | + dashboardId, | |
63 | + hideDashboardToolbar | |
64 | + }; | |
65 | + this.dashboardService.setTenantHomeDashboardInfo(homeDashboardInfo).subscribe( | |
66 | + () => { | |
67 | + this.setHomeDashboardInfo(homeDashboardInfo); | |
68 | + } | |
69 | + ); | |
70 | + } | |
71 | + | |
72 | + confirmForm(): FormGroup { | |
73 | + return this.homeSettings; | |
74 | + } | |
75 | + | |
76 | + private setHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo) { | |
77 | + this.homeSettings.reset({ | |
78 | + dashboardId: homeDashboardInfo?.dashboardId?.id, | |
79 | + hideDashboardToolbar: isDefinedAndNotNull(homeDashboardInfo?.hideDashboardToolbar) ? | |
80 | + homeDashboardInfo?.hideDashboardToolbar : true | |
81 | + }); | |
82 | + } | |
83 | + | |
84 | +} | ... | ... |
... | ... | @@ -72,6 +72,21 @@ |
72 | 72 | <mat-label translate>customer.description</mat-label> |
73 | 73 | <textarea matInput formControlName="description" rows="2"></textarea> |
74 | 74 | </mat-form-field> |
75 | + <section class="tb-default-dashboard" fxFlex fxLayout="column" *ngIf="entity?.id"> | |
76 | + <section fxFlex fxLayout="column" fxLayout.gt-sm="row"> | |
77 | + <tb-dashboard-autocomplete | |
78 | + fxFlex | |
79 | + placeholder="{{ 'dashboard.home-dashboard' | translate }}" | |
80 | + formControlName="homeDashboardId" | |
81 | + [dashboardsScope]="'customer'" | |
82 | + [customerId]="entity?.id.id" | |
83 | + [selectFirstDashboard]="false" | |
84 | + ></tb-dashboard-autocomplete> | |
85 | + <mat-checkbox fxFlex formControlName="homeDashboardHideToolbar"> | |
86 | + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} | |
87 | + </mat-checkbox> | |
88 | + </section> | |
89 | + </section> | |
75 | 90 | </div> |
76 | 91 | <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact> |
77 | 92 | </fieldset> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +@import "../../../../../scss/constants"; | |
17 | + | |
18 | +:host { | |
19 | + .tb-default-dashboard { | |
20 | + tb-dashboard-autocomplete { | |
21 | + @media #{$mat-gt-sm} { | |
22 | + padding-right: 12px; | |
23 | + } | |
24 | + | |
25 | + @media #{$mat-lt-md} { | |
26 | + padding-bottom: 12px; | |
27 | + } | |
28 | + } | |
29 | + mat-checkbox { | |
30 | + @media #{$mat-gt-sm} { | |
31 | + margin-top: 16px; | |
32 | + } | |
33 | + } | |
34 | + } | |
35 | +} | ... | ... |
... | ... | @@ -23,10 +23,12 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti |
23 | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | 24 | import { ContactBasedComponent } from '../../components/entity/contact-based.component'; |
25 | 25 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
26 | +import { isDefinedAndNotNull } from '@core/utils'; | |
26 | 27 | |
27 | 28 | @Component({ |
28 | 29 | selector: 'tb-customer', |
29 | - templateUrl: './customer.component.html' | |
30 | + templateUrl: './customer.component.html', | |
31 | + styleUrls: ['./customer.component.scss'] | |
30 | 32 | }) |
31 | 33 | export class CustomerComponent extends ContactBasedComponent<Customer> { |
32 | 34 | |
... | ... | @@ -54,7 +56,10 @@ export class CustomerComponent extends ContactBasedComponent<Customer> { |
54 | 56 | title: [entity ? entity.title : '', [Validators.required]], |
55 | 57 | additionalInfo: this.fb.group( |
56 | 58 | { |
57 | - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] | |
59 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | |
60 | + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], | |
61 | + homeDashboardHideToolbar: [entity && entity.additionalInfo && | |
62 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] | |
58 | 63 | } |
59 | 64 | ) |
60 | 65 | } |
... | ... | @@ -65,6 +70,11 @@ export class CustomerComponent extends ContactBasedComponent<Customer> { |
65 | 70 | this.isPublic = entity.additionalInfo && entity.additionalInfo.isPublic; |
66 | 71 | this.entityForm.patchValue({title: entity.title}); |
67 | 72 | this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); |
73 | + this.entityForm.patchValue({additionalInfo: | |
74 | + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); | |
75 | + this.entityForm.patchValue({additionalInfo: | |
76 | + {homeDashboardHideToolbar: entity.additionalInfo && | |
77 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); | |
68 | 78 | } |
69 | 79 | |
70 | 80 | onCustomerIdCopied(event) { | ... | ... |
... | ... | @@ -14,11 +14,25 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { NgModule } from '@angular/core'; | |
18 | -import { RouterModule, Routes } from '@angular/router'; | |
17 | +import { Injectable, NgModule } from '@angular/core'; | |
18 | +import { Resolve, RouterModule, Routes } from '@angular/router'; | |
19 | 19 | |
20 | 20 | import { HomeLinksComponent } from './home-links.component'; |
21 | 21 | import { Authority } from '@shared/models/authority.enum'; |
22 | +import { Observable } from 'rxjs'; | |
23 | +import { HomeDashboard } from '@shared/models/dashboard.models'; | |
24 | +import { DashboardService } from '@core/http/dashboard.service'; | |
25 | + | |
26 | +@Injectable() | |
27 | +export class HomeDashboardResolver implements Resolve<HomeDashboard> { | |
28 | + | |
29 | + constructor(private dashboardService: DashboardService) { | |
30 | + } | |
31 | + | |
32 | + resolve(): Observable<HomeDashboard> { | |
33 | + return this.dashboardService.getHomeDashboard(); | |
34 | + } | |
35 | +} | |
22 | 36 | |
23 | 37 | const routes: Routes = [ |
24 | 38 | { |
... | ... | @@ -31,12 +45,18 @@ const routes: Routes = [ |
31 | 45 | label: 'home.home', |
32 | 46 | icon: 'home' |
33 | 47 | } |
48 | + }, | |
49 | + resolve: { | |
50 | + homeDashboard: HomeDashboardResolver | |
34 | 51 | } |
35 | 52 | } |
36 | 53 | ]; |
37 | 54 | |
38 | 55 | @NgModule({ |
39 | 56 | imports: [RouterModule.forChild(routes)], |
40 | - exports: [RouterModule] | |
57 | + exports: [RouterModule], | |
58 | + providers: [ | |
59 | + HomeDashboardResolver | |
60 | + ] | |
41 | 61 | }) |
42 | 62 | export class HomeLinksRoutingModule { } | ... | ... |
... | ... | @@ -15,23 +15,26 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<mat-grid-list class="tb-home-links" [cols]="cols" rowHeight="280px"> | |
19 | - <mat-grid-tile [colspan]="sectionColspan(section)" *ngFor="let section of homeSections$| async"> | |
20 | - <mat-card style="width: 100%;"> | |
21 | - <mat-card-title> | |
22 | - <span translate class="mat-headline">{{section.name}}</span> | |
23 | - </mat-card-title> | |
24 | - <mat-card-content> | |
25 | - <mat-grid-list rowHeight="170px" [cols]="section.places.length"> | |
26 | - <mat-grid-tile *ngFor="let place of section.places"> | |
27 | - <a mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}"> | |
28 | - <mat-icon *ngIf="!place.isMdiIcon" class="material-icons tb-mat-96">{{place.icon}}</mat-icon> | |
29 | - <mat-icon *ngIf="place.isMdiIcon" class="tb-mat-96" [svgIcon]="place.icon"></mat-icon> | |
30 | - <span translate>{{place.name}}</span> | |
31 | - </a> | |
32 | - </mat-grid-tile> | |
33 | - </mat-grid-list> | |
34 | - </mat-card-content> | |
35 | - </mat-card> | |
36 | - </mat-grid-tile> | |
37 | -</mat-grid-list> | |
18 | +<tb-dashboard-page *ngIf="homeDashboard; else homeLinks" [embedded]="true" [dashboard]="homeDashboard" [hideToolbar]="homeDashboard.hideDashboardToolbar"></tb-dashboard-page> | |
19 | +<ng-template #homeLinks> | |
20 | + <mat-grid-list class="tb-home-links" [cols]="cols" rowHeight="280px"> | |
21 | + <mat-grid-tile [colspan]="sectionColspan(section)" *ngFor="let section of homeSections$| async"> | |
22 | + <mat-card style="width: 100%;"> | |
23 | + <mat-card-title> | |
24 | + <span translate class="mat-headline">{{section.name}}</span> | |
25 | + </mat-card-title> | |
26 | + <mat-card-content> | |
27 | + <mat-grid-list rowHeight="170px" [cols]="section.places.length"> | |
28 | + <mat-grid-tile *ngFor="let place of section.places"> | |
29 | + <a mat-raised-button color="primary" class="tb-card-button" routerLink="{{place.path}}"> | |
30 | + <mat-icon *ngIf="!place.isMdiIcon" class="material-icons tb-mat-96">{{place.icon}}</mat-icon> | |
31 | + <mat-icon *ngIf="place.isMdiIcon" class="tb-mat-96" [svgIcon]="place.icon"></mat-icon> | |
32 | + <span translate>{{place.name}}</span> | |
33 | + </a> | |
34 | + </mat-grid-tile> | |
35 | + </mat-grid-list> | |
36 | + </mat-card-content> | |
37 | + </mat-card> | |
38 | + </mat-grid-tile> | |
39 | + </mat-grid-list> | |
40 | +</ng-template> | ... | ... |
... | ... | @@ -19,6 +19,8 @@ import { MenuService } from '@core/services/menu.service'; |
19 | 19 | import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; |
20 | 20 | import { MediaBreakpoints } from '@shared/models/constants'; |
21 | 21 | import { HomeSection } from '@core/services/menu.models'; |
22 | +import { ActivatedRoute } from '@angular/router'; | |
23 | +import { HomeDashboard } from '@shared/models/dashboard.models'; | |
22 | 24 | |
23 | 25 | @Component({ |
24 | 26 | selector: 'tb-home-links', |
... | ... | @@ -31,15 +33,20 @@ export class HomeLinksComponent implements OnInit { |
31 | 33 | |
32 | 34 | cols = 2; |
33 | 35 | |
36 | + homeDashboard: HomeDashboard = this.route.snapshot.data.homeDashboard; | |
37 | + | |
34 | 38 | constructor(private menuService: MenuService, |
35 | - public breakpointObserver: BreakpointObserver) { | |
39 | + public breakpointObserver: BreakpointObserver, | |
40 | + private route: ActivatedRoute) { | |
36 | 41 | } |
37 | 42 | |
38 | 43 | ngOnInit() { |
39 | - this.updateColumnCount(); | |
40 | - this.breakpointObserver | |
41 | - .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) | |
42 | - .subscribe((state: BreakpointState) => this.updateColumnCount()); | |
44 | + if (!this.homeDashboard) { | |
45 | + this.updateColumnCount(); | |
46 | + this.breakpointObserver | |
47 | + .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) | |
48 | + .subscribe((state: BreakpointState) => this.updateColumnCount()); | |
49 | + } | |
43 | 50 | } |
44 | 51 | |
45 | 52 | private updateColumnCount() { | ... | ... |
... | ... | @@ -20,6 +20,7 @@ import { CommonModule } from '@angular/common'; |
20 | 20 | import { HomeLinksRoutingModule } from './home-links-routing.module'; |
21 | 21 | import { HomeLinksComponent } from './home-links.component'; |
22 | 22 | import { SharedModule } from '@app/shared/shared.module'; |
23 | +import { HomeComponentsModule } from '@home/components/home-components.module'; | |
23 | 24 | |
24 | 25 | @NgModule({ |
25 | 26 | declarations: |
... | ... | @@ -29,6 +30,7 @@ import { SharedModule } from '@app/shared/shared.module'; |
29 | 30 | imports: [ |
30 | 31 | CommonModule, |
31 | 32 | SharedModule, |
33 | + HomeComponentsModule, | |
32 | 34 | HomeLinksRoutingModule |
33 | 35 | ] |
34 | 36 | }) | ... | ... |
... | ... | @@ -63,6 +63,20 @@ |
63 | 63 | </mat-option> |
64 | 64 | </mat-select> |
65 | 65 | </mat-form-field> |
66 | + <section class="tb-home-dashboard" fxFlex fxLayout="column" fxLayout.gt-sm="row"> | |
67 | + <tb-dashboard-autocomplete | |
68 | + fxFlex | |
69 | + placeholder="{{ 'dashboard.home-dashboard' | translate }}" | |
70 | + formControlName="homeDashboardId" | |
71 | + [dashboardsScope]="user?.authority === authorities.TENANT_ADMIN ? 'tenant' : 'customer'" | |
72 | + [tenantId]="user?.tenantId?.id" | |
73 | + [customerId]="user?.customerId?.id" | |
74 | + [selectFirstDashboard]="false" | |
75 | + ></tb-dashboard-autocomplete> | |
76 | + <mat-checkbox fxFlex formControlName="homeDashboardHideToolbar"> | |
77 | + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} | |
78 | + </mat-checkbox> | |
79 | + </section> | |
66 | 80 | <div fxLayout="row" style="padding-bottom: 16px;"> |
67 | 81 | <button mat-button mat-raised-button color="primary" |
68 | 82 | type="button" | ... | ... |
... | ... | @@ -38,5 +38,21 @@ |
38 | 38 | font-size: 16px; |
39 | 39 | font-weight: 400; |
40 | 40 | } |
41 | + .tb-home-dashboard { | |
42 | + tb-dashboard-autocomplete { | |
43 | + @media #{$mat-gt-sm} { | |
44 | + padding-right: 12px; | |
45 | + } | |
46 | + | |
47 | + @media #{$mat-lt-md} { | |
48 | + padding-bottom: 12px; | |
49 | + } | |
50 | + } | |
51 | + mat-checkbox { | |
52 | + @media #{$mat-gt-sm} { | |
53 | + margin-top: 16px; | |
54 | + } | |
55 | + } | |
56 | + } | |
41 | 57 | } |
42 | 58 | } | ... | ... |
... | ... | @@ -32,6 +32,7 @@ import { MatDialog } from '@angular/material/dialog'; |
32 | 32 | import { DialogService } from '@core/services/dialog.service'; |
33 | 33 | import { AuthService } from '@core/auth/auth.service'; |
34 | 34 | import { ActivatedRoute } from '@angular/router'; |
35 | +import { isDefinedAndNotNull } from '@core/utils'; | |
35 | 36 | |
36 | 37 | @Component({ |
37 | 38 | selector: 'tb-profile', |
... | ... | @@ -66,7 +67,9 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir |
66 | 67 | email: ['', [Validators.required, Validators.email]], |
67 | 68 | firstName: [''], |
68 | 69 | lastName: [''], |
69 | - language: [''] | |
70 | + language: [''], | |
71 | + homeDashboardId: [null], | |
72 | + homeDashboardHideToolbar: [true] | |
70 | 73 | }); |
71 | 74 | } |
72 | 75 | |
... | ... | @@ -76,6 +79,8 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir |
76 | 79 | this.user.additionalInfo = {}; |
77 | 80 | } |
78 | 81 | this.user.additionalInfo.lang = this.profile.get('language').value; |
82 | + this.user.additionalInfo.homeDashboardId = this.profile.get('homeDashboardId').value; | |
83 | + this.user.additionalInfo.homeDashboardHideToolbar = this.profile.get('homeDashboardHideToolbar').value; | |
79 | 84 | this.userService.saveUser(this.user).subscribe( |
80 | 85 | (user) => { |
81 | 86 | this.userLoaded(user); |
... | ... | @@ -106,12 +111,23 @@ export class ProfileComponent extends PageComponent implements OnInit, HasConfir |
106 | 111 | this.user = user; |
107 | 112 | this.profile.reset(user); |
108 | 113 | let lang; |
109 | - if (user.additionalInfo && user.additionalInfo.lang) { | |
110 | - lang = user.additionalInfo.lang; | |
111 | - } else { | |
114 | + let homeDashboardId; | |
115 | + let homeDashboardHideToolbar = true; | |
116 | + if (user.additionalInfo) { | |
117 | + if (user.additionalInfo.lang) { | |
118 | + lang = user.additionalInfo.lang; | |
119 | + } | |
120 | + homeDashboardId = user.additionalInfo.homeDashboardId; | |
121 | + if (isDefinedAndNotNull(user.additionalInfo.homeDashboardHideToolbar)) { | |
122 | + homeDashboardHideToolbar = user.additionalInfo.homeDashboardHideToolbar; | |
123 | + } | |
124 | + } | |
125 | + if (!lang) { | |
112 | 126 | lang = this.translate.currentLang; |
113 | 127 | } |
114 | 128 | this.profile.get('language').setValue(lang); |
129 | + this.profile.get('homeDashboardId').setValue(homeDashboardId); | |
130 | + this.profile.get('homeDashboardHideToolbar').setValue(homeDashboardHideToolbar); | |
115 | 131 | } |
116 | 132 | |
117 | 133 | confirmForm(): FormGroup { | ... | ... |
... | ... | @@ -60,6 +60,21 @@ |
60 | 60 | <mat-label translate>tenant.description</mat-label> |
61 | 61 | <textarea matInput formControlName="description" rows="2"></textarea> |
62 | 62 | </mat-form-field> |
63 | + <section class="tb-default-dashboard" fxFlex fxLayout="column" *ngIf="entity?.id"> | |
64 | + <section fxFlex fxLayout="column" fxLayout.gt-sm="row"> | |
65 | + <tb-dashboard-autocomplete | |
66 | + fxFlex | |
67 | + placeholder="{{ 'dashboard.home-dashboard' | translate }}" | |
68 | + formControlName="homeDashboardId" | |
69 | + [dashboardsScope]="'tenant'" | |
70 | + [tenantId]="entity?.id.id" | |
71 | + [selectFirstDashboard]="false" | |
72 | + ></tb-dashboard-autocomplete> | |
73 | + <mat-checkbox fxFlex formControlName="homeDashboardHideToolbar"> | |
74 | + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} | |
75 | + </mat-checkbox> | |
76 | + </section> | |
77 | + </section> | |
63 | 78 | </div> |
64 | 79 | <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact> |
65 | 80 | </fieldset> | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2021 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +@import "../../../../../scss/constants"; | |
17 | + | |
18 | +:host { | |
19 | + .tb-default-dashboard { | |
20 | + tb-dashboard-autocomplete { | |
21 | + @media #{$mat-gt-sm} { | |
22 | + padding-right: 12px; | |
23 | + } | |
24 | + | |
25 | + @media #{$mat-lt-md} { | |
26 | + padding-bottom: 12px; | |
27 | + } | |
28 | + } | |
29 | + mat-checkbox { | |
30 | + @media #{$mat-gt-sm} { | |
31 | + margin-top: 16px; | |
32 | + } | |
33 | + } | |
34 | + } | |
35 | +} | ... | ... |
... | ... | @@ -23,11 +23,12 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti |
23 | 23 | import { TranslateService } from '@ngx-translate/core'; |
24 | 24 | import { ContactBasedComponent } from '../../components/entity/contact-based.component'; |
25 | 25 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
26 | +import { isDefinedAndNotNull } from '@core/utils'; | |
26 | 27 | |
27 | 28 | @Component({ |
28 | 29 | selector: 'tb-tenant', |
29 | 30 | templateUrl: './tenant.component.html', |
30 | - styleUrls: [] | |
31 | + styleUrls: ['./tenant.component.scss'] | |
31 | 32 | }) |
32 | 33 | export class TenantComponent extends ContactBasedComponent<TenantInfo> { |
33 | 34 | |
... | ... | @@ -54,7 +55,10 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { |
54 | 55 | tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], |
55 | 56 | additionalInfo: this.fb.group( |
56 | 57 | { |
57 | - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] | |
58 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | |
59 | + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], | |
60 | + homeDashboardHideToolbar: [entity && entity.additionalInfo && | |
61 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] | |
58 | 62 | } |
59 | 63 | ) |
60 | 64 | } |
... | ... | @@ -65,6 +69,11 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { |
65 | 69 | this.entityForm.patchValue({title: entity.title}); |
66 | 70 | this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId}); |
67 | 71 | this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); |
72 | + this.entityForm.patchValue({additionalInfo: | |
73 | + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); | |
74 | + this.entityForm.patchValue({additionalInfo: | |
75 | + {homeDashboardHideToolbar: entity.additionalInfo && | |
76 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); | |
68 | 77 | } |
69 | 78 | |
70 | 79 | updateFormState() { | ... | ... |
... | ... | @@ -95,6 +95,20 @@ |
95 | 95 | {{ 'user.always-fullscreen' | translate }} |
96 | 96 | </mat-checkbox> |
97 | 97 | </section> |
98 | + <section fxFlex fxLayout="column" fxLayout.gt-sm="row"> | |
99 | + <tb-dashboard-autocomplete | |
100 | + fxFlex | |
101 | + placeholder="{{ 'dashboard.home-dashboard' | translate }}" | |
102 | + formControlName="homeDashboardId" | |
103 | + [dashboardsScope]="entity?.authority === authority.TENANT_ADMIN ? 'tenant' : 'customer'" | |
104 | + [tenantId]="entity?.tenantId?.id" | |
105 | + [customerId]="entity?.customerId?.id" | |
106 | + [selectFirstDashboard]="false" | |
107 | + ></tb-dashboard-autocomplete> | |
108 | + <mat-checkbox fxFlex formControlName="homeDashboardHideToolbar"> | |
109 | + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} | |
110 | + </mat-checkbox> | |
111 | + </section> | |
98 | 112 | </section> |
99 | 113 | </div> |
100 | 114 | </fieldset> | ... | ... |
... | ... | @@ -23,7 +23,7 @@ import { User } from '@shared/models/user.model'; |
23 | 23 | import { selectAuth } from '@core/auth/auth.selectors'; |
24 | 24 | import { map } from 'rxjs/operators'; |
25 | 25 | import { Authority } from '@shared/models/authority.enum'; |
26 | -import { isUndefined } from '@core/utils'; | |
26 | +import { isDefinedAndNotNull, isUndefined } from '@core/utils'; | |
27 | 27 | import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; |
28 | 28 | |
29 | 29 | @Component({ |
... | ... | @@ -74,6 +74,9 @@ export class UserComponent extends EntityComponent<User> { |
74 | 74 | description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], |
75 | 75 | defaultDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null], |
76 | 76 | defaultDashboardFullscreen: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false], |
77 | + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], | |
78 | + homeDashboardHideToolbar: [entity && entity.additionalInfo && | |
79 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] | |
77 | 80 | } |
78 | 81 | ) |
79 | 82 | } |
... | ... | @@ -89,6 +92,11 @@ export class UserComponent extends EntityComponent<User> { |
89 | 92 | {defaultDashboardId: entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null}}); |
90 | 93 | this.entityForm.patchValue({additionalInfo: |
91 | 94 | {defaultDashboardFullscreen: entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false}}); |
95 | + this.entityForm.patchValue({additionalInfo: | |
96 | + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); | |
97 | + this.entityForm.patchValue({additionalInfo: | |
98 | + {homeDashboardHideToolbar: entity.additionalInfo && | |
99 | + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); | |
92 | 100 | } |
93 | 101 | |
94 | 102 | } | ... | ... |
... | ... | @@ -35,6 +35,7 @@ |
35 | 35 | [isEditActionEnabled]="true" |
36 | 36 | [isExportActionEnabled]="true" |
37 | 37 | [isRemoveActionEnabled]="!isReadOnly" |
38 | + [disableWidgetInteraction]="true" | |
38 | 39 | [callbacks]="dashboardCallbacks"></tb-dashboard> |
39 | 40 | <tb-footer-fab-buttons [fxShow]="!isReadOnly" [footerFabButtons]="footerFabButtons"> |
40 | 41 | </tb-footer-fab-buttons> | ... | ... |
... | ... | @@ -14,7 +14,17 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, forwardRef, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | |
17 | +import { | |
18 | + Component, | |
19 | + forwardRef, | |
20 | + Inject, | |
21 | + Injector, | |
22 | + Input, | |
23 | + OnInit, | |
24 | + StaticProvider, | |
25 | + ViewChild, | |
26 | + ViewContainerRef | |
27 | +} from '@angular/core'; | |
18 | 28 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | 29 | import { Observable, of } from 'rxjs'; |
20 | 30 | import { PageLink } from '@shared/models/page/page-link'; |
... | ... | @@ -32,7 +42,7 @@ import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef |
32 | 42 | import { BreakpointObserver } from '@angular/cdk/layout'; |
33 | 43 | import { DOCUMENT } from '@angular/common'; |
34 | 44 | import { WINDOW } from '@core/services/window.service'; |
35 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
45 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
36 | 46 | import { |
37 | 47 | DASHBOARD_SELECT_PANEL_DATA, |
38 | 48 | DashboardSelectPanelComponent, |
... | ... | @@ -186,12 +196,12 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit { |
186 | 196 | overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector)); |
187 | 197 | } |
188 | 198 | |
189 | - private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): PortalInjector { | |
190 | - const injectionTokens = new WeakMap<any, any>([ | |
191 | - [DASHBOARD_SELECT_PANEL_DATA, data], | |
192 | - [OverlayRef, overlayRef] | |
193 | - ]); | |
194 | - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
199 | + private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): Injector { | |
200 | + const providers: StaticProvider[] = [ | |
201 | + {provide: DASHBOARD_SELECT_PANEL_DATA, useValue: data}, | |
202 | + {provide: OverlayRef, useValue: overlayRef} | |
203 | + ]; | |
204 | + return Injector.create({parent: this.viewContainerRef.injector, providers}); | |
195 | 205 | } |
196 | 206 | |
197 | 207 | private updateView() { | ... | ... |
... | ... | @@ -28,12 +28,17 @@ class ThingsboardRadios extends React.Component<JsonFormFieldProps, JsonFormFiel |
28 | 28 | ); |
29 | 29 | }); |
30 | 30 | |
31 | + let row = false; | |
32 | + if (this.props.form.direction === 'row') { | |
33 | + row = true; | |
34 | + } | |
35 | + | |
31 | 36 | return ( |
32 | 37 | <FormControl component='fieldset' |
33 | 38 | className={this.props.form.htmlClass} |
34 | 39 | disabled={this.props.form.readonly}> |
35 | 40 | <FormLabel component='legend'>{this.props.form.title}</FormLabel> |
36 | - <RadioGroup name={this.props.form.title} value={this.props.value} onChange={(e) => { | |
41 | + <RadioGroup row={row} name={this.props.form.title} value={this.props.value} onChange={(e) => { | |
37 | 42 | this.props.onChangeValidate(e); |
38 | 43 | }}> |
39 | 44 | {items} | ... | ... |
... | ... | @@ -22,6 +22,7 @@ import { |
22 | 22 | KeyLabelItem |
23 | 23 | } from '@shared/components/json-form/react/json-form.models'; |
24 | 24 | import { Mode } from 'rc-select/lib/interface'; |
25 | +import { deepClone } from '@core/utils'; | |
25 | 26 | |
26 | 27 | interface ThingsboardRcSelectState extends JsonFormFieldState { |
27 | 28 | currentValue: KeyLabelItem | KeyLabelItem[]; |
... | ... | @@ -151,10 +152,14 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar |
151 | 152 | labelClass += ' tb-focused'; |
152 | 153 | } |
153 | 154 | let mode: Mode; |
154 | - if (this.props.form.tags) { | |
155 | - mode = 'tags'; | |
156 | - } else if (this.props.form.multiple) { | |
157 | - mode = 'multiple'; | |
155 | + let value = this.state.currentValue; | |
156 | + if (this.props.form.tags || this.props.form.multiple) { | |
157 | + value = deepClone(value); | |
158 | + if (this.props.form.tags) { | |
159 | + mode = 'tags'; | |
160 | + } else if (this.props.form.multiple) { | |
161 | + mode = 'multiple'; | |
162 | + } | |
158 | 163 | } |
159 | 164 | |
160 | 165 | const dropdownStyle = {...this.props.form.dropdownStyle, ...{zIndex: 100001}}; |
... | ... | @@ -176,12 +181,13 @@ class ThingsboardRcSelect extends React.Component<JsonFormFieldProps, Thingsboar |
176 | 181 | maxTagTextLength={this.props.form.maxTagTextLength} |
177 | 182 | disabled={this.props.form.readonly} |
178 | 183 | optionLabelProp='children' |
179 | - value={this.state.currentValue} | |
184 | + value={value} | |
180 | 185 | labelInValue={true} |
181 | 186 | onSelect={this.onSelect} |
182 | 187 | onDeselect={this.onDeselect} |
183 | 188 | onFocus={this.onFocus} |
184 | 189 | onBlur={this.onBlur} |
190 | + placeholder={this.props.form.placeholder} | |
185 | 191 | style={this.props.form.style || {width: '100%'}}> |
186 | 192 | {options} |
187 | 193 | </Select> | ... | ... |
... | ... | @@ -14,7 +14,18 @@ |
14 | 14 | /// limitations under the License. |
15 | 15 | /// |
16 | 16 | |
17 | -import { Component, forwardRef, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; | |
17 | +import { | |
18 | + Component, | |
19 | + forwardRef, | |
20 | + Inject, | |
21 | + Injector, | |
22 | + Input, | |
23 | + OnDestroy, | |
24 | + OnInit, | |
25 | + StaticProvider, | |
26 | + ViewChild, | |
27 | + ViewContainerRef | |
28 | +} from '@angular/core'; | |
18 | 29 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; |
19 | 30 | import { TranslateService } from '@ngx-translate/core'; |
20 | 31 | import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; |
... | ... | @@ -32,7 +43,7 @@ import { |
32 | 43 | TimewindowPanelComponent, |
33 | 44 | TimewindowPanelData |
34 | 45 | } from '@shared/components/time/timewindow-panel.component'; |
35 | -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; | |
46 | +import { ComponentPortal } from '@angular/cdk/portal'; | |
36 | 47 | import { MediaBreakpoints } from '@shared/models/constants'; |
37 | 48 | import { BreakpointObserver } from '@angular/cdk/layout'; |
38 | 49 | import { WINDOW } from '@core/services/window.service'; |
... | ... | @@ -229,12 +240,12 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces |
229 | 240 | }); |
230 | 241 | } |
231 | 242 | |
232 | - private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): PortalInjector { | |
233 | - const injectionTokens = new WeakMap<any, any>([ | |
234 | - [TIMEWINDOW_PANEL_DATA, data], | |
235 | - [OverlayRef, overlayRef] | |
236 | - ]); | |
237 | - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
243 | + private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): Injector { | |
244 | + const providers: StaticProvider[] = [ | |
245 | + {provide: TIMEWINDOW_PANEL_DATA, useValue: data}, | |
246 | + {provide: OverlayRef, useValue: overlayRef} | |
247 | + ]; | |
248 | + return Injector.create({parent: this.viewContainerRef.injector, providers}); | |
238 | 249 | } |
239 | 250 | |
240 | 251 | registerOnChange(fn: any): void { | ... | ... |
... | ... | @@ -20,9 +20,11 @@ import { |
20 | 20 | Directive, |
21 | 21 | ElementRef, HostBinding, |
22 | 22 | Inject, |
23 | + Injector, | |
23 | 24 | Input, |
24 | 25 | NgZone, |
25 | 26 | OnDestroy, Optional, |
27 | + StaticProvider, | |
26 | 28 | ViewChild, |
27 | 29 | ViewContainerRef |
28 | 30 | } from '@angular/core'; |
... | ... | @@ -34,7 +36,6 @@ import { BreakpointObserver } from '@angular/cdk/layout'; |
34 | 36 | import { MediaBreakpoints } from '@shared/models/constants'; |
35 | 37 | import { MatButton } from '@angular/material/button'; |
36 | 38 | import Timeout = NodeJS.Timeout; |
37 | -import { PortalInjector } from '@angular/cdk/portal'; | |
38 | 39 | |
39 | 40 | @Directive({ |
40 | 41 | selector: '[tb-toast]' |
... | ... | @@ -138,10 +139,10 @@ export class ToastDirective implements AfterViewInit, OnDestroy { |
138 | 139 | this.toastComponentRef.destroy(); |
139 | 140 | } |
140 | 141 | }; |
141 | - const injectionTokens = new WeakMap<any, any>([ | |
142 | - [MAT_SNACK_BAR_DATA, data] | |
143 | - ]); | |
144 | - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); | |
142 | + const providers: StaticProvider[] = [ | |
143 | + {provide: MAT_SNACK_BAR_DATA, useValue: data} | |
144 | + ]; | |
145 | + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); | |
145 | 146 | this.toastComponentRef = this.viewContainerRef.createComponent(componentFactory, 0, injector); |
146 | 147 | this.cd.detectChanges(); |
147 | 148 | ... | ... |
... | ... | @@ -106,6 +106,15 @@ export interface Dashboard extends DashboardInfo { |
106 | 106 | configuration?: DashboardConfiguration; |
107 | 107 | } |
108 | 108 | |
109 | +export interface HomeDashboard extends Dashboard { | |
110 | + hideDashboardToolbar: boolean; | |
111 | +} | |
112 | + | |
113 | +export interface HomeDashboardInfo { | |
114 | + dashboardId: DashboardId; | |
115 | + hideDashboardToolbar: boolean; | |
116 | +} | |
117 | + | |
109 | 118 | export function isPublicDashboard(dashboard: DashboardInfo): boolean { |
110 | 119 | if (dashboard && dashboard.assignedCustomers) { |
111 | 120 | return dashboard.assignedCustomers | ... | ... |
... | ... | @@ -54,7 +54,10 @@ |
54 | 54 | "share-via": "Sdílet přes {{provider}}", |
55 | 55 | "continue": "Pokračovat", |
56 | 56 | "discard-changes": "Zahodit změny", |
57 | - "download": "Stáhnout" | |
57 | + "download": "Stáhnout", | |
58 | + "next-with-label": "Další: {{label}}", | |
59 | + "read-more": "Zobrazit více", | |
60 | + "hide": "Skrýt" | |
58 | 61 | }, |
59 | 62 | "aggregation": { |
60 | 63 | "aggregation": "Agregace", |
... | ... | @@ -77,6 +80,8 @@ |
77 | 80 | "test-mail-sent": "Testovací zpráva byla úspěšně odeslána!", |
78 | 81 | "base-url": "Základní URL", |
79 | 82 | "base-url-required": "Hodnota Základní URL je povinná.", |
83 | + "prohibit-different-url": "Zakázat použití názvu hosta z hlaviček požadavku klienta", | |
84 | + "prohibit-different-url-hint": "Toto nastavení by mělo být povoleno v produkčních prostředích. Pokud je zakázáno, může způsobit bezpečnostní problémy", | |
80 | 85 | "mail-from": "Email od", |
81 | 86 | "mail-from-required": "Hodnota Email od je povinná.", |
82 | 87 | "smtp-protocol": "SMTP protokol", |
... | ... | @@ -99,6 +104,33 @@ |
99 | 104 | "proxy-user": "Uživatel proxy", |
100 | 105 | "proxy-password": "Heslo proxy", |
101 | 106 | "send-test-mail": "Odeslat testovací zprávu", |
107 | + "sms-provider": "Poskytovatel SMS", | |
108 | + "sms-provider-settings": "Nastavení poskytovatele SMS", | |
109 | + "sms-provider-type": "Typ poskytovatele SMS", | |
110 | + "sms-provider-type-required": "Typ poskytovatele SMS je povinný.", | |
111 | + "sms-provider-type-aws-sns": "Amazon SNS", | |
112 | + "sms-provider-type-twilio": "Twilio", | |
113 | + "aws-access-key-id": "AWS Access Key ID", | |
114 | + "aws-access-key-id-required": "AWS Access Key ID je povinný", | |
115 | + "aws-secret-access-key": "AWS Secret Access Key", | |
116 | + "aws-secret-access-key-required": "AWS Secret Access Key je povinný", | |
117 | + "aws-region": "AWS Region", | |
118 | + "aws-region-required": "AWS Region je povinný", | |
119 | + "number-from": "Telefonní číslo odesílatele", | |
120 | + "number-from-required": "Telefonní číslo Odesílatele je povinné.", | |
121 | + "number-to": "Telefonní číslo příjemce", | |
122 | + "number-to-required": "Telefonní číslo příjemce je povinné.", | |
123 | + "phone-number-hint": "Telefonní číslo ve formátu E.164, např. +19995550123", | |
124 | + "phone-number-pattern": "Neplatné telefonní číslo. Mělo by odpovídat formátu E.164, např. +19995550123.", | |
125 | + "sms-message": "SMS zpráva", | |
126 | + "sms-message-required": "SMS zpráva je povinná.", | |
127 | + "sms-message-max-length": "SMS zpráva nemůže být delší než 1600 znaků", | |
128 | + "twilio-account-sid": "Twilio Account SID", | |
129 | + "twilio-account-sid-required": "Twilio Account SID je povinné", | |
130 | + "twilio-account-token": "Twilio Account Token", | |
131 | + "twilio-account-token-required": "Twilio Account Token je povinný", | |
132 | + "send-test-sms": "Odeslat testovací SMS", | |
133 | + "test-sms-sent": "Testovací SMS úspěšně odeslána!", | |
102 | 134 | "security-settings": "Bezpečnostní nastavení", |
103 | 135 | "password-policy": "Politika hesel", |
104 | 136 | "minimum-password-length": "Minimální délka hesla", |
... | ... | @@ -119,8 +151,74 @@ |
119 | 151 | "general-policy": "Obecná politika", |
120 | 152 | "max-failed-login-attempts": "Maximální počet neúspěšných pokusů o přihlášení před zablokováním účtu", |
121 | 153 | "minimum-max-failed-login-attempts-range": "Maximální počet neúspěšných pokusů o přihlášení před zablokováním účtu nemůže být záporný", |
122 | - "user-lockout-notification-email": "V případě zablokování uživatelského účtu odeslat upozornění na email" | |
123 | - }, | |
154 | + "user-lockout-notification-email": "V případě zablokování uživatelského účtu odeslat upozornění na email", | |
155 | + "domain-name": "Doménové jméno", | |
156 | + "domain-name-unique": "Doménové jméno a protokol musí být unikátní.", | |
157 | + "error-verification-url": "Doménové jméno by nemělo obsahovat symbol '/' ani ':'. Příklad: thingsboard.io", | |
158 | + "oauth2": { | |
159 | + "access-token-uri": "URI přístupového tokenu", | |
160 | + "access-token-uri-required": "URI přístupového tokenu je povinné.", | |
161 | + "activate-user": "Aktivovat uživatele", | |
162 | + "add-domain": "Přidat doménu", | |
163 | + "delete-domain": "Smazat doménu", | |
164 | + "add-provider": "Přidat poskytovatele", | |
165 | + "delete-provider": "Smazat poskytovatele", | |
166 | + "allow-user-creation": "Povolit vytvoření uživatele", | |
167 | + "always-fullscreen": "Vždy v režimu celé obrazovky", | |
168 | + "authorization-uri": "Autorizační URI", | |
169 | + "authorization-uri-required": "Autorizační URI je povinné.", | |
170 | + "client-authentication-method": "Metoda autentizace klienta", | |
171 | + "client-id": "ID klienta", | |
172 | + "client-id-required": "ID klienta je povinné.", | |
173 | + "client-secret": "Heslo klienta", | |
174 | + "client-secret-required": "Heslo klienta je povinné.", | |
175 | + "custom-setting": "Vlastní nastavení", | |
176 | + "customer-name-pattern": "Vzor názvu zákazníka", | |
177 | + "default-dashboard-name": "Název defaultního dashboardu", | |
178 | + "delete-domain-text": "Budťe opatrní, protože po potvrzení nebudou doména ani žádná data poskytovatele dostupné.", | |
179 | + "delete-domain-title": "Jste si jisti, že chcete smazat nastavení domény '{{domainName}}'?", | |
180 | + "delete-registration-text": "Buďte opatrní, protože po potvrzení nebudou data poskytovatele dostupná.", | |
181 | + "delete-registration-title": "Jste si jisti, že chcete smazat poskytovatele '{{name}}'?", | |
182 | + "email-attribute-key": "Atribut klíče email", | |
183 | + "email-attribute-key-required": "Atribut klíče email je povinný.", | |
184 | + "first-name-attribute-key": "Atribut klíče jméno", | |
185 | + "general": "Obecné", | |
186 | + "jwk-set-uri": "JSON Web Key URI", | |
187 | + "last-name-attribute-key": "Atribut klíče příjmení", | |
188 | + "login-button-icon": "Ikona tlačítka přihlášení", | |
189 | + "login-button-label": "Označení poskytovatele", | |
190 | + "login-button-label-placeholder": "Přihlásit se přes $(Provider label)", | |
191 | + "login-button-label-required": "Označení je povinné.", | |
192 | + "login-provider": "Poskytovatel přihlášení", | |
193 | + "mapper": "Mapper", | |
194 | + "new-domain": "Nová doména", | |
195 | + "oauth2": "OAuth2", | |
196 | + "redirect-uri-template": "Šablona URI přesměrování", | |
197 | + "copy-redirect-uri": "Zkopírovat URI přesměrování", | |
198 | + "registration-id": "ID registrace", | |
199 | + "registration-id-required": "ID registrace je povinné.", | |
200 | + "registration-id-unique": "Id registrace musí být v systému unikátní.", | |
201 | + "scope": "Rozsah", | |
202 | + "scope-required": "Rozsah je povinný.", | |
203 | + "tenant-name-pattern": "Vzor názvu tenanta", | |
204 | + "tenant-name-pattern-required": "Vzor názvu tenanta je povinný.", | |
205 | + "tenant-name-strategy": "Strategie názvu tenanta", | |
206 | + "type": "Typ mapperu", | |
207 | + "uri-pattern-error": "Neplatný formát URI.", | |
208 | + "url": "URL", | |
209 | + "url-pattern": "Neplatný formát URL.", | |
210 | + "url-required": "URL je povinná.", | |
211 | + "user-info-uri": "User info URI", | |
212 | + "user-info-uri-required": "User info URI je povinné.", | |
213 | + "user-name-attribute-name": "Atribut klíče název uživatele", | |
214 | + "user-name-attribute-name-required": "Atribut klíče název uživatele je povinný", | |
215 | + "protocol": "Protokol", | |
216 | + "domain-schema-http": "HTTP", | |
217 | + "domain-schema-https": "HTTPS", | |
218 | + "domain-schema-mixed": "HTTP+HTTPS", | |
219 | + "enable": "Povolit nastavení OAuth2" | |
220 | + } | |
221 | + }, | |
124 | 222 | "alarm": { |
125 | 223 | "alarm": "Alarm", |
126 | 224 | "alarms": "Alarmy", |
... | ... | @@ -128,6 +226,8 @@ |
128 | 226 | "no-alarms-matching": "Žádné alarmy odpovídající '{{entity}}' nebyly nalezeny.", |
129 | 227 | "alarm-required": "Alarm je povinný", |
130 | 228 | "alarm-status": "Stav alarmu", |
229 | + "alarm-status-list": "Seznam stavů alarmu", | |
230 | + "any-status": "Všechny stavy", | |
131 | 231 | "search-status": { |
132 | 232 | "ANY": "Všechny", |
133 | 233 | "ACTIVE": "Aktivní", |
... | ... | @@ -154,6 +254,8 @@ |
154 | 254 | "end-time": "Datum ukončení", |
155 | 255 | "ack-time": "Datum přijetí", |
156 | 256 | "clear-time": "Datum vyřešení", |
257 | + "alarm-severity-list": "Seznam závažností alarmu", | |
258 | + "any-severity": "Všechny závažnosti", | |
157 | 259 | "severity-critical": "Kritická", |
158 | 260 | "severity-major": "Vysoká", |
159 | 261 | "severity-minor": "Nízká", |
... | ... | @@ -176,12 +278,16 @@ |
176 | 278 | "clear-alarm-title": "Odstranit alarm", |
177 | 279 | "clear-alarm-text": "Jste si jisti, že chcete alarm odstranit?", |
178 | 280 | "alarm-status-filter": "Filtr stavu alarmu", |
281 | + "alarm-filter": "Filtr alarmu", | |
179 | 282 | "max-count-load": "Maximální počet nahraných alarmů (0 - neomezeně)", |
180 | 283 | "max-count-load-required": "Maximální počet nahraných alarmů je povinný.", |
181 | 284 | "max-count-load-error-min": "Minimální hodnota je 0.", |
182 | 285 | "fetch-size": "Velikost dávky", |
183 | 286 | "fetch-size-required": "Velikost dávky je povinná.", |
184 | - "fetch-size-error-min": "Minimální hodnota je 10." | |
287 | + "fetch-size-error-min": "Minimální hodnota je 10.", | |
288 | + "alarm-type-list": "Seznam typů alarmu", | |
289 | + "any-type": "Všechny typy", | |
290 | + "search-propagated-alarms": "Vyhledat zpropagované alarmy" | |
185 | 291 | }, |
186 | 292 | "alias": { |
187 | 293 | "add": "Přidat alias", |
... | ... | @@ -211,6 +317,7 @@ |
211 | 317 | "filter-type-device-search-query-description": "Zařízení typů {{deviceTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", |
212 | 318 | "filter-type-entity-view-search-query": "Dotaz na vyhledání zobrazení entity", |
213 | 319 | "filter-type-entity-view-search-query-description": "Entitní pohledy typů {{entityViewTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", |
320 | + "filter-type-apiUsageState": "Stav využití Api", | |
214 | 321 | "entity-filter": "Filtr entity", |
215 | 322 | "resolve-multiple": "Použít jako více entit", |
216 | 323 | "filter-type": "Typ filtru", |
... | ... | @@ -325,6 +432,59 @@ |
325 | 432 | "no-attributes-text": "Žádné atributy nebyly nalezeny", |
326 | 433 | "no-telemetry-text": "Žádná telemetrie nebyla nalezena" |
327 | 434 | }, |
435 | + "api-usage": { | |
436 | + "api-usage": "Využití Api", | |
437 | + "data-points": "Datové body", | |
438 | + "data-points-storage-days": "Dny uložení datových bodů", | |
439 | + "email": "Email", | |
440 | + "email-messages": "Emailové zprávy", | |
441 | + "email-messages-daily-activity": "Denní aktivita emailových zpráv", | |
442 | + "email-messages-hourly-activity": "Hodinová aktivita emailových zpráv", | |
443 | + "email-messages-monthly-activity": "Měsíční aktivita emailových zpráv", | |
444 | + "exceptions": "Výjimky", | |
445 | + "executions": "Zpracování", | |
446 | + "javascript": "JavaScript", | |
447 | + "javascript-executions": "JavaScript výjimky", | |
448 | + "javascript-functions": "JavaScript funkce", | |
449 | + "javascript-functions-daily-activity": "Denní aktivita JavaScript funkcí", | |
450 | + "javascript-functions-hourly-activity": "Hodinová aktivita JavaScript funkcí", | |
451 | + "javascript-functions-monthly-activity": "Měsíční aktivita JavaScript funkcí", | |
452 | + "latest-error": "Poslední chyba", | |
453 | + "messages": "Zprávy", | |
454 | + "permanent-failures": "${entityName} permanentní chyby", | |
455 | + "permanent-timeouts": "${entityName} permanentní timeouty", | |
456 | + "processing-failures": "${entityName} chyby zpracování", | |
457 | + "processing-failures-and-timeouts": "Chyby a timeouty zpracování", | |
458 | + "processing-timeouts": "${entityName} timeouty zpracování", | |
459 | + "queue-stats": "Statistiky fronty", | |
460 | + "rule-chain": "Řetěz pravidel", | |
461 | + "rule-engine": "Engine pro zpracování pravidel", | |
462 | + "rule-engine-daily-activity": "Denní aktivita enginu pro zpracování pravidel", | |
463 | + "rule-engine-executions": "Zpracování Enginu pro zpracování pravidel", | |
464 | + "rule-engine-hourly-activity": "Hodinová aktivita enginu pro zpracování pravidel", | |
465 | + "rule-engine-monthly-activity": "Měsíční aktivita enginu pro zpracování pravidel", | |
466 | + "rule-engine-statistics": "Statistiky enginu pro zpracování pravidel", | |
467 | + "rule-node": "Uzel pravidla", | |
468 | + "sms": "SMS", | |
469 | + "sms-messages": "SMS zprávy", | |
470 | + "sms-messages-daily-activity": "Denní aktivita SMS zpráv", | |
471 | + "sms-messages-hourly-activity": "Hodinová aktivita SMS zpráv", | |
472 | + "sms-messages-monthly-activity": "Měsíční aktivita SMS zpráv", | |
473 | + "successful": "${entityName} úspěšnost", | |
474 | + "telemetry": "Telemetrie", | |
475 | + "telemetry-persistence": "Uložení telemetrie", | |
476 | + "telemetry-persistence-daily-activity": "Denní aktivita uložení telemetrie", | |
477 | + "telemetry-persistence-hourly-activity": "Hodinová aktivita uložení telemetrie", | |
478 | + "telemetry-persistence-monthly-activity": "Měsíční aktivita uložení telemetrie", | |
479 | + "transport": "Přenos", | |
480 | + "transport-daily-activity": "Denní aktivita přenosu", | |
481 | + "transport-data-points": "Datové body přenosu", | |
482 | + "transport-hourly-activity": "Hodinová aktivita přenosu", | |
483 | + "transport-messages": "Zprávy přenosu", | |
484 | + "transport-monthly-activity": "Měsíční aktivita přenosu", | |
485 | + "view-details": "Zobrazit detail", | |
486 | + "view-statistics": "Zobrazit statistiky" | |
487 | + }, | |
328 | 488 | "audit-log": { |
329 | 489 | "audit": "Audit", |
330 | 490 | "audit-logs": "Záznamy auditu", |
... | ... | @@ -363,7 +523,13 @@ |
363 | 523 | "action-data": "Data akce", |
364 | 524 | "failure-details": "Detail chyby", |
365 | 525 | "search": "Prohledat záznamy auditu", |
366 | - "clear-search": "Vymazat vyhledávání" | |
526 | + "clear-search": "Vymazat vyhledávání", | |
527 | + "type-assigned-from-tenant": "Odebráno tenantovi", | |
528 | + "type-assigned-to-tenant": "Přiřazeno tenantovi", | |
529 | + "type-provision-success": "Zřízení zařízení", | |
530 | + "type-provision-failure": "Selhání zřízení zařízení", | |
531 | + "type-timeseries-updated": "Aktualizace telemetrie", | |
532 | + "type-timeseries-deleted": "Smazání telemetrie" | |
367 | 533 | }, |
368 | 534 | "confirm-on-exit": { |
369 | 535 | "message": "Některé změny nebyly uloženy. Jste si jisti, že chcete tuto stránku opustit?", |
... | ... | @@ -549,6 +715,7 @@ |
549 | 715 | "title-color": "Barva názvu", |
550 | 716 | "display-dashboards-selection": "Zobrazit výběr dashboardů", |
551 | 717 | "display-entities-selection": "Zobrazit výběr entit", |
718 | + "display-filters": "Zobrazit filtry", | |
552 | 719 | "display-dashboard-timewindow": "Zobrazit časové okno", |
553 | 720 | "display-dashboard-export": "Zobrazit export", |
554 | 721 | "import": "Importovat dashboard", |
... | ... | @@ -615,6 +782,7 @@ |
615 | 782 | "alarm": "Pole alarmu", |
616 | 783 | "timeseries-required": "Časové řady entity jsou povinné.", |
617 | 784 | "timeseries-or-attributes-required": "Časové řady / atributy entity jsou povinné.", |
785 | + "alarm-fields-timeseries-or-attributes-required": "Pole alarmu nebo časové řady / atributy jsou povinné.", | |
618 | 786 | "maximum-timeseries-or-attributes": "Maximálně { count, plural, 1 {1 časová řada/atribut je povolena.} other {# časových řad/atributů je povoleno} }", |
619 | 787 | "alarm-fields-required": "Pole alarmu jsou povinná.", |
620 | 788 | "function-types": "Typy funkcí", |
... | ... | @@ -706,6 +874,12 @@ |
706 | 874 | "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 20 znaků.", |
707 | 875 | "rsa-key": "RSA veřejný klíč", |
708 | 876 | "rsa-key-required": "RSA veřejný klíč je povinný.", |
877 | + "client-id": "ID klienta", | |
878 | + "client-id-pattern": "Obsahuje neplatné znaky.", | |
879 | + "user-name": "Název uživatele", | |
880 | + "user-name-required": "Název uživatele je povinný.", | |
881 | + "client-id-or-user-name-necessary": "ID klienta a/nebo název uživatele jsou povinné", | |
882 | + "password": "Heslo", | |
709 | 883 | "secret": "Heslo", |
710 | 884 | "secret-required": "Heslo je povinné.", |
711 | 885 | "device-type": "Typ zařízení", |
... | ... | @@ -724,19 +898,183 @@ |
724 | 898 | "details": "Detail", |
725 | 899 | "copyId": "Kopírovat Id zařízení", |
726 | 900 | "copyAccessToken": "Kopírovat přístupový token", |
901 | + "copy-mqtt-authentication": "Kopírovat přístupové údaje MQTT", | |
727 | 902 | "idCopiedMessage": "Id zařízení bylo zkopírováno do schránky", |
728 | 903 | "accessTokenCopiedMessage": "Přístupový token zařízení byl zkopírován do schránky", |
904 | + "mqtt-authentication-copied-message": "MQTT autentizace zařízení byla zkopírována do schránky", | |
729 | 905 | "assignedToCustomer": "Přiřazeno zákazníkovi", |
730 | 906 | "unable-delete-device-alias-title": "Nebylo možné smazat alias zařízení", |
731 | 907 | "unable-delete-device-alias-text": "Alias zařízení '{{deviceAlias}}' nelze smazat, protože je používán následujícími widgety:<br/>{{widgetsList}}", |
732 | 908 | "is-gateway": "Je bránou", |
909 | + "overwrite-activity-time": "Přepsat čas aktivity připojeného zařízení", | |
733 | 910 | "public": "Veřejné", |
734 | 911 | "device-public": "Zařízení je veřejné", |
735 | 912 | "select-device": "Vybrat zařízení", |
736 | 913 | "import": "Importovat zařízení", |
737 | 914 | "device-file": "Soubor zařízení", |
738 | 915 | "search": "Vyhledat zařízení", |
739 | - "selected-devices": "Vybráno { count, plural, 1 {1 zařízení} other {# zařízení} }" | |
916 | + "selected-devices": "Vybráno { count, plural, 1 {1 zařízení} other {# zařízení} }", | |
917 | + "device-configuration": "Konfigurace zařízení", | |
918 | + "transport-configuration": "Konfigurace přenosu", | |
919 | + "wizard": { | |
920 | + "device-wizard": "Průvodce zařízením", | |
921 | + "device-details": "Detail zařízení", | |
922 | + "new-device-profile": "Vytvořit nový profil zařízení", | |
923 | + "existing-device-profile": "Vybrat existující profil zařízení", | |
924 | + "specific-configuration": "Specifická konfigurace", | |
925 | + "customer-to-assign-device": "Přiřadit zařízení zákazníkovi", | |
926 | + "add-credential": "Přidat přístupový údaj" | |
927 | + } | |
928 | + }, | |
929 | + "device-profile": { | |
930 | + "device-profile": "Profil zařízení", | |
931 | + "device-profiles": "Profily zařízení", | |
932 | + "all-device-profiles": "Všechny", | |
933 | + "add": "Přidat profil zařízení", | |
934 | + "edit": "Editovat profil zařízení", | |
935 | + "device-profile-details": "Detail profilu zařízení", | |
936 | + "no-device-profiles-text": "Žádné profily zařízení nebyly nalezeny", | |
937 | + "search": "Vyhledat profily zařízení", | |
938 | + "selected-device-profiles": "Vybráno { count, plural, 1 {1 profil zařízení} other {# profilů zařízení} }", | |
939 | + "no-device-profiles-matching": "Žádný profil zařízení odpovídající '{{entity}}' nebyl nalezen.", | |
940 | + "device-profile-required": "Profil zařízení je povinný", | |
941 | + "idCopiedMessage": "Id profilu zařízení bylo zkopírováno do schránky", | |
942 | + "set-default": "Učinit profil zařízení defaultním", | |
943 | + "delete": "Smazat profil zařízení", | |
944 | + "copyId": "Kopírovat Id profilu zařízení", | |
945 | + "new-device-profile-name": "Název profilu zařízení", | |
946 | + "new-device-profile-name-required": "Název profilu zařízení je povinný.", | |
947 | + "name": "Název", | |
948 | + "name-required": "Název je povinný.", | |
949 | + "type": "Typ profilu", | |
950 | + "type-required": "Typ profilu je povinný.", | |
951 | + "type-default": "Defaultní", | |
952 | + "transport-type": "Typ přenosu", | |
953 | + "transport-type-required": "Typ přenosu je povinný.", | |
954 | + "transport-type-default": "Defaultní", | |
955 | + "transport-type-default-hint": "Podporuje základní MQTT, HTTP and CoAP přenos", | |
956 | + "transport-type-mqtt": "MQTT", | |
957 | + "transport-type-mqtt-hint": "Umožňuje pokročilé nastavení MQTT přenosu", | |
958 | + "transport-type-lwm2m": "LWM2M", | |
959 | + "transport-type-lwm2m-hint": "Typ transportu LWM2M", | |
960 | + "description": "Popis", | |
961 | + "default": "Defaultní", | |
962 | + "profile-configuration": "Konfigurace profilu", | |
963 | + "transport-configuration": "Konfigurace přenosu", | |
964 | + "default-rule-chain": "Defaultní řetěz pravidel", | |
965 | + "select-queue-hint": "Vyberte z rozbalovacího seznamu nebo přidejte vlastní název.", | |
966 | + "delete-device-profile-title": "Jste si jisti, že chcete smazat profil zařízení '{{deviceProfileName}}'?", | |
967 | + "delete-device-profile-text": "Buďte opatrní, protože po potvrzení nebude možné profil zařízení ani žádná související data obnovit.", | |
968 | + "delete-device-profiles-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 profil zařízení} other {# profilů zařízení} }?", | |
969 | + "delete-device-profiles-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané profily zařízení odstraněny a žádná související data nebude možné obnovit.", | |
970 | + "set-default-device-profile-title": "Jste si jisti, že chcete profil zařízení '{{deviceProfileName}}' učinit defaultním?", | |
971 | + "set-default-device-profile-text": "Po potvrzení bude profil zařízení označen jako defaultní a bude použit pro nová zařízení bez specifikovaného profilu.", | |
972 | + "no-device-profiles-found": "Žádné profily zařízení nebyly nalezeny.", | |
973 | + "create-new-device-profile": "Vytvořit nový!", | |
974 | + "mqtt-device-topic-filters": "Filtry MQTT fronty zařízení", | |
975 | + "mqtt-device-topic-filters-unique": "Filtry MQTT fronty zařízení musí být unikátní.", | |
976 | + "mqtt-device-payload-type": "MQTT zpráva zařízení", | |
977 | + "mqtt-device-payload-type-json": "JSON", | |
978 | + "mqtt-device-payload-type-proto": "Protobuf", | |
979 | + "mqtt-payload-type-required": "Typ zprávy je povinný.", | |
980 | + "support-level-wildcards": "Jsou podporovány jednoúrovňové <code>[+]</code> a víceúrovňové <code>[#]</code> zástupné znaky.", | |
981 | + "telemetry-topic-filter": "Filtr fronty telemetrie", | |
982 | + "telemetry-topic-filter-required": "Filtr fronty telemetrie je povinný.", | |
983 | + "attributes-topic-filter": "Filtr atributů fronty", | |
984 | + "attributes-topic-filter-required": "Filtr atributů fronty je povinný.", | |
985 | + "telemetry-proto-schema": "Proto schéma telemetrie", | |
986 | + "telemetry-proto-schema-required": "Proto schéma telemetrie je povinné.", | |
987 | + "attributes-proto-schema": "Atributy proto schémata", | |
988 | + "attributes-proto-schema-required": "Atributy proto schémata jsou povinné.", | |
989 | + "rpc-response-topic-filter": "Filtr fronty RPC odpovědi", | |
990 | + "rpc-response-topic-filter-required": "Filtr fronty RPC odpovědi je povinný.", | |
991 | + "not-valid-pattern-topic-filter": "Neplatný vzor filtru fronty", | |
992 | + "not-valid-single-character": "Neplatné použití jednoúrovňového zástupného znaku", | |
993 | + "not-valid-multi-character": "Neplatné použití víceúrovňového zástupného znaku", | |
994 | + "single-level-wildcards-hint": "<code>[+]</code> je vhodný pro jakoukoli úroveň filtru fronty. Př.: <b>v1/devices/+/telemetry</b> or <b>+/devices/+/attributes</b>.", | |
995 | + "multi-level-wildcards-hint": "<code>[#]</code> může nahradit filtr fronty a může se jednat o poslední symbol fronty. Př.: <b>#</b> or <b>v1/devices/me/#</b>.", | |
996 | + "alarm-rules": "Pravidla alarmu", | |
997 | + "alarm-rules-with-count": "Pravidla alarmu ({{count}})", | |
998 | + "no-alarm-rules": "Žádná pravidla alarmu nejsou konfigurována", | |
999 | + "add-alarm-rule": "Přidat pravidlo alarmu", | |
1000 | + "edit-alarm-rule": "Editovat pravidlo alarmu", | |
1001 | + "alarm-type": "Typ alarmu", | |
1002 | + "alarm-type-required": "Typ alarmu je povinný.", | |
1003 | + "alarm-type-unique": "Typ alarmu musí být v rámci pravidel alarmu profilu zařízení unikátní.", | |
1004 | + "create-alarm-pattern": "Vytvořit <b>{{alarmType}}</b> alarm", | |
1005 | + "create-alarm-rules": "Vytvořit pravidla alarmu", | |
1006 | + "no-create-alarm-rules": "Nejsou konfigurovány žádné podmínky vytvoření", | |
1007 | + "add-create-alarm-rule-prompt": "Přidejte prosím pravidlo vytvoření alarmu", | |
1008 | + "clear-alarm-rule": "Pravidlo zrušení alarmu", | |
1009 | + "no-clear-alarm-rule": "Není konfigurována žádná podmínka zrušení", | |
1010 | + "add-create-alarm-rule": "Přidat podmínku vytvoření", | |
1011 | + "add-clear-alarm-rule": "Přidat podmínku zrušení", | |
1012 | + "select-alarm-severity": "Vybrat závažnost alarmu", | |
1013 | + "alarm-severity-required": "Závažnost alarmu je povinná.", | |
1014 | + "condition-duration": "Doba trvání podmínky", | |
1015 | + "condition-duration-value": "Hodnota doby trvání", | |
1016 | + "condition-duration-time-unit": "Jednotka času", | |
1017 | + "condition-duration-value-range": "Hodnota doby trvání musí být v rozsahu od 1 do 2147483647.", | |
1018 | + "condition-duration-value-pattern": "Doba trvání musí být celé číslo.", | |
1019 | + "condition-duration-value-required": "Doba trvání je povinná.", | |
1020 | + "condition-duration-time-unit-required": "Jednotka času je povinná.", | |
1021 | + "advanced-settings": "Pokročilá nastavení", | |
1022 | + "alarm-rule-details": "Detail", | |
1023 | + "add-alarm-rule-details": "Přidat detail", | |
1024 | + "propagate-alarm": "Propagovat alarm", | |
1025 | + "alarm-rule-relation-types-list": "Typy vztahů ke zpropagování", | |
1026 | + "alarm-rule-relation-types-list-hint": "Pokud nejsou vybrány žádné typy vztahů, alarmy budou propagovány bez filtru typu vztahu.", | |
1027 | + "alarm-details": "Detail alarmu", | |
1028 | + "alarm-rule-condition": "Podmínka pravidla alarmu", | |
1029 | + "enter-alarm-rule-condition-prompt": "Přidejte prosím podmínku pravidla alarmu", | |
1030 | + "edit-alarm-rule-condition": "Editovat podmínku pravidla alarmu", | |
1031 | + "device-provisioning": "Zřízení zařízení", | |
1032 | + "provision-strategy": "Strategie zřízení", | |
1033 | + "provision-strategy-required": "Strategie zřízení je povinná.", | |
1034 | + "provision-strategy-disabled": "Zakázáno", | |
1035 | + "provision-strategy-created-new": "Povolit vytváření nových zařízení", | |
1036 | + "provision-strategy-check-pre-provisioned": "Zkontrolovat předvytvořená zařízení", | |
1037 | + "provision-device-key": "Klíč pro zřízení zařízení", | |
1038 | + "provision-device-key-required": "Klíč pro zřízení zařízení je povinný.", | |
1039 | + "copy-provision-key": "Kopírovat klíč pro zřízení", | |
1040 | + "provision-key-copied-message": "Klíč pro zřízení byl zkopírován do schránky", | |
1041 | + "provision-device-secret": "Heslo pro zřízení zařízení", | |
1042 | + "provision-device-secret-required": "Heslo pro zřízení zařízení je povinné.", | |
1043 | + "copy-provision-secret": "Kopírovat heslo pro zřízení", | |
1044 | + "provision-secret-copied-message": "Heslo pro zřízení zařízení bylo zkopírováno do schránky", | |
1045 | + "condition": "Podmínka", | |
1046 | + "condition-type": "Typ podmínky", | |
1047 | + "condition-type-simple": "Jednoduchá", | |
1048 | + "condition-type-duration": "Doba trvání", | |
1049 | + "condition-during": "V průběhu {{during}}", | |
1050 | + "condition-type-repeating": "Opakování", | |
1051 | + "condition-type-required": "Typ podmínky je povinný.", | |
1052 | + "condition-repeating-value": "Počet událostí", | |
1053 | + "condition-repeating-value-range": "Počet událostí musí být v rozsahu od 1 do 2147483647.", | |
1054 | + "condition-repeating-value-pattern": "Počet událostí musí být celé číslo.", | |
1055 | + "condition-repeating-value-required": "Počet událostí je povinný.", | |
1056 | + "condition-repeat-times": "Opakování { count, plural, 1 {1 krát} other {# krát} }", | |
1057 | + "schedule-type": "Typ plánovače", | |
1058 | + "schedule-type-required": "Typ plánovače je povinný.", | |
1059 | + "schedule": "Časový plán", | |
1060 | + "edit-schedule": "Editovat časový plán alarmu", | |
1061 | + "schedule-any-time": "Aktivní neustále", | |
1062 | + "schedule-specific-time": "Aktivní v konkrétním čase", | |
1063 | + "schedule-custom": "Vlastní", | |
1064 | + "schedule-day": { | |
1065 | + "monday": "Pondělí", | |
1066 | + "tuesday": "Úterý", | |
1067 | + "wednesday": "Středa", | |
1068 | + "thursday": "Čtvrtek", | |
1069 | + "friday": "Pátek", | |
1070 | + "saturday": "Sobota", | |
1071 | + "sunday": "Neděle" | |
1072 | + }, | |
1073 | + "schedule-days": "Dny", | |
1074 | + "schedule-time": "Čas", | |
1075 | + "schedule-time-from": "Od", | |
1076 | + "schedule-time-to": "Do", | |
1077 | + "schedule-days-of-week-required": "Musí být vybrán minimálně jeden den v týdnu." | |
740 | 1078 | }, |
741 | 1079 | "dialog": { |
742 | 1080 | "close": "Zavřít dialog" |
... | ... | @@ -757,7 +1095,7 @@ |
757 | 1095 | "entity-alias": "Alias entity", |
758 | 1096 | "unable-delete-entity-alias-title": "Alias entity nebylo možné smazat", |
759 | 1097 | "unable-delete-entity-alias-text": "Alias entity '{{entityAlias}}' nelze smazat, protože je používán následujícími widgety:<br/>{{widgetsList}}", |
760 | - "duplicate-alias-error": "Nalezen dupliticní alias '{{alias}}'.<br>Aliasy entit musí být v rámci dashboardu unikátní.", | |
1098 | + "duplicate-alias-error": "Nalezen duplicitní alias '{{alias}}'.<br>Aliasy entit musí být v rámci dashboardu unikátní.", | |
761 | 1099 | "missing-entity-filter-error": "Ve filtru chybí alias '{{alias}}'.", |
762 | 1100 | "configure-alias": "Konfigurovat '{{alias}}' alias", |
763 | 1101 | "alias": "Alias", |
... | ... | @@ -794,6 +1132,10 @@ |
794 | 1132 | "type-devices": "Zařízení", |
795 | 1133 | "list-of-devices": "{ count, plural, 1 {Jedno zařízení} other {Seznam # zařízení} }", |
796 | 1134 | "device-name-starts-with": "Zařízení, jejichž název začíná '{{prefix}}'", |
1135 | + "type-device-profile": "Profil zařízení", | |
1136 | + "type-device-profiles": "Profily zařízení", | |
1137 | + "list-of-device-profiles": "{ count, plural, 1 {Jeden profil zařízení} other {Seznam # profilů zařízení} }", | |
1138 | + "device-profile-name-starts-with": "Profily zařízení, jejichž název začíná '{{prefix}}'", | |
797 | 1139 | "type-asset": "Aktivum", |
798 | 1140 | "type-assets": "Aktiva", |
799 | 1141 | "list-of-assets": "{ count, plural, 1 {Jedno aktivum} other {Seznam # aktiv} }", |
... | ... | @@ -814,6 +1156,10 @@ |
814 | 1156 | "type-tenants": "Tenanti", |
815 | 1157 | "list-of-tenants": "{ count, plural, 1 {Jeden tenant} other {Seznam # tenantů} }", |
816 | 1158 | "tenant-name-starts-with": "Tenanti, jejichž název začíná '{{prefix}}'", |
1159 | + "type-tenant-profile": "Profil tenanta", | |
1160 | + "type-tenant-profiles": "Profily tenantů", | |
1161 | + "list-of-tenant-profiles": "{ count, plural, 1 {Jeden profil tenanta} other {Seznam # profilů tenantů} }", | |
1162 | + "tenant-profile-name-starts-with": "Profily tenantů, jejichž název začíná '{{prefix}}'", | |
817 | 1163 | "type-customer": "Zákazník", |
818 | 1164 | "type-customers": "Zákazníci", |
819 | 1165 | "list-of-customers": "{ count, plural, 1 {Jeden zákazník} other {Seznam # zákazníků} }", |
... | ... | @@ -840,6 +1186,8 @@ |
840 | 1186 | "rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'", |
841 | 1187 | "type-current-customer": "Stávající zákazník", |
842 | 1188 | "type-current-tenant": "Stávající tenant", |
1189 | + "type-current-user": "Stávající uživatel", | |
1190 | + "type-current-user-owner": "Vlastník stávajícího uživatele", | |
843 | 1191 | "search": "Vyhledat entity", |
844 | 1192 | "selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno", |
845 | 1193 | "entity-name": "Název entity", |
... | ... | @@ -847,7 +1195,8 @@ |
847 | 1195 | "details": "Detail entity", |
848 | 1196 | "no-entities-prompt": "Žádné entity nebyly nalezeny", |
849 | 1197 | "no-data": "Nelze zobrazit žádná data", |
850 | - "columns-to-display": "Zobrazit sloupce" | |
1198 | + "columns-to-display": "Zobrazit sloupce", | |
1199 | + "type-api-usage-state": "Stav využití API" | |
851 | 1200 | }, |
852 | 1201 | "entity-field": { |
853 | 1202 | "created-time": "Datum vytvoření", |
... | ... | @@ -1048,7 +1397,7 @@ |
1048 | 1397 | "anonymous": "Anonymní", |
1049 | 1398 | "basic": "Základní", |
1050 | 1399 | "pem": "PEM", |
1051 | - "ca-cert": "soubor CA certifikátu *", | |
1400 | + "ca-cert": "Soubor CA certifikátu *", | |
1052 | 1401 | "private-key": "Soubor privátního klíče *", |
1053 | 1402 | "cert": "Soubor certifikátu *", |
1054 | 1403 | "no-file": "Žádný soubor nebyl vybrán.", |
... | ... | @@ -1154,6 +1503,93 @@ |
1154 | 1503 | "file": "Soubor rozšíření", |
1155 | 1504 | "invalid-file-error": "Neplatný soubor rozšíření" |
1156 | 1505 | }, |
1506 | + "filter": { | |
1507 | + "add": "Přidat filtr", | |
1508 | + "edit": "Editovat filtr", | |
1509 | + "name": "Název filtru", | |
1510 | + "name-required": "Název filtru je povinný.", | |
1511 | + "duplicate-filter": "Filtr s identickým názvem již existuje.", | |
1512 | + "filters": "Filtry", | |
1513 | + "unable-delete-filter-title": "Smazat filtr není možné", | |
1514 | + "unable-delete-filter-text": "Filtr '{{filter}}' není možné smazat, protože je používán následujícím widgetem(y):<br/>{{widgetsList}}", | |
1515 | + "duplicate-filter-error": "Nalezen duplicitní filtr '{{filter}}'.<br>Filtry musí být v rámci dashboardu unikátní.", | |
1516 | + "missing-key-filters-error": "U filtru '{{filter}}' chybí klíčové filtry.", | |
1517 | + "filter": "Filtr", | |
1518 | + "editable": "Editovatelné", | |
1519 | + "no-filters-found": "Žádné filtry nebyly nalezeny.", | |
1520 | + "no-filter-text": "Není specifikován žádný filtr", | |
1521 | + "add-filter-prompt": "Přidejte prosím filtr", | |
1522 | + "no-filter-matching": "'{{filter}}' nebyl nalezen.", | |
1523 | + "create-new-filter": "Vytvořit nový!", | |
1524 | + "filter-required": "Filtr je povinný.", | |
1525 | + "operation": { | |
1526 | + "operation": "Operace", | |
1527 | + "equal": "je rovno", | |
1528 | + "not-equal": "není rovno", | |
1529 | + "starts-with": "začíná na", | |
1530 | + "ends-with": "končí na", | |
1531 | + "contains": "obsahuje", | |
1532 | + "not-contains": "neobsahuje", | |
1533 | + "greater": "větší než", | |
1534 | + "less": "menší než", | |
1535 | + "greater-or-equal": "větší nebo rovno", | |
1536 | + "less-or-equal": "menší nebo rovno", | |
1537 | + "and": "a", | |
1538 | + "or": "nebo" | |
1539 | + }, | |
1540 | + "ignore-case": "ignorovat velikost písmen", | |
1541 | + "value": "Hodnota", | |
1542 | + "remove-filter": "Odebrat filtr", | |
1543 | + "preview": "Náhled filtru", | |
1544 | + "no-filters": "Nejsou konfigurovány žádné filtry", | |
1545 | + "add-filter": "Přidat filtr", | |
1546 | + "add-complex-filter": "Přidat komplexní filtr", | |
1547 | + "add-complex": "Přidat komplex", | |
1548 | + "complex-filter": "Komplexní filtr", | |
1549 | + "edit-complex-filter": "Editovat komplexní filtr", | |
1550 | + "edit-filter-user-params": "Editovat filtr predikátu parametrů uživatele", | |
1551 | + "filter-user-params": "Filtr predikátu parametrů uživatele", | |
1552 | + "user-parameters": "Parametry uživatele", | |
1553 | + "display-label": "Zobrazované označení", | |
1554 | + "autogenerated-label": "Automaticky vygenerovat označení", | |
1555 | + "order-priority": "Priority pořadí polí", | |
1556 | + "key-filter": "Klíčový filtr", | |
1557 | + "key-filters": "Klíčové filtry", | |
1558 | + "key-name": "Název klíče", | |
1559 | + "key-name-required": "Název klíče je povinný.", | |
1560 | + "key-type": { | |
1561 | + "key-type": "Typ klíče", | |
1562 | + "attribute": "Atribut", | |
1563 | + "timeseries": "Časové řady", | |
1564 | + "entity-field": "Pole entity" | |
1565 | + }, | |
1566 | + "value-type": { | |
1567 | + "value-type": "Typ hodnoty", | |
1568 | + "string": "Řetězec", | |
1569 | + "numeric": "Číslo", | |
1570 | + "boolean": "Pravdivostní hodnota", | |
1571 | + "date-time": "Datum a čas" | |
1572 | + }, | |
1573 | + "value-type-required": "Typ hodnoty klíče je povinný.", | |
1574 | + "key-value-type-change-title": "Jste si jisti, že chcete změnit typ klíče hodnoty?", | |
1575 | + "key-value-type-change-message": "Pokud potvrdíte nový typ hodnoty, všechny zadané klíčové filtry budou odstraněny.", | |
1576 | + "no-key-filters": "Nejsou konfigurovány žádné klíčové filtry", | |
1577 | + "add-key-filter": "Přidat klíčový filtr", | |
1578 | + "remove-key-filter": "Odebrat klíčový filtr", | |
1579 | + "edit-key-filter": "Editovat klíčový filtr", | |
1580 | + "date": "Datum", | |
1581 | + "time": "Čas", | |
1582 | + "current-tenant": "Stávající tenant", | |
1583 | + "current-customer": "Stávající zákazník", | |
1584 | + "current-user": "Stávající uživatel", | |
1585 | + "current-device": "Stávající zařízení", | |
1586 | + "default-value": "Defaultní hodnota", | |
1587 | + "dynamic-source-type": "Dynamický typ zdroje", | |
1588 | + "no-dynamic-value": "Žádná dynamická hodnota", | |
1589 | + "source-attribute": "Atribut zdroje", | |
1590 | + "switch-to-dynamic-value": "Přepnout na dynamickou hodnotu", | |
1591 | + "switch-to-default-value": "Přepnout na defaultní hodnotu" | |
1592 | + }, | |
1157 | 1593 | "fullscreen": { |
1158 | 1594 | "expand": "Rozšířit do režimu celé obrazovky", |
1159 | 1595 | "exit": "Ukončit režim celé obrazovky", |
... | ... | @@ -1286,6 +1722,7 @@ |
1286 | 1722 | "entity-field": "Pole entity", |
1287 | 1723 | "access-token": "Přístupový token", |
1288 | 1724 | "isgateway": "Je bránou", |
1725 | + "activity-time-from-gateway-device": "Čas aktivity ze zařízení brány", | |
1289 | 1726 | "description": "Popis" |
1290 | 1727 | }, |
1291 | 1728 | "stepper-text":{ |
... | ... | @@ -1329,6 +1766,7 @@ |
1329 | 1766 | "legend": { |
1330 | 1767 | "direction": "Směr legendy", |
1331 | 1768 | "position": "Pozice legendy", |
1769 | + "sort-legend": "Setřídit datové klíče v legendě", | |
1332 | 1770 | "show-max": "Zobrazit max hodnotu", |
1333 | 1771 | "show-min": "Zobrazit min hodnotu", |
1334 | 1772 | "show-avg": "Zobrazit průměrnou hodnotu", |
... | ... | @@ -1525,6 +1963,12 @@ |
1525 | 1963 | "help": "Nápověda", |
1526 | 1964 | "reset-debug-mode": "Resetovat režim ladění na všech uzlech" |
1527 | 1965 | }, |
1966 | + "timezone": { | |
1967 | + "timezone": "Časová zóna", | |
1968 | + "select-timezone": "Vyberte časovou zónu", | |
1969 | + "no-timezones-matching": "žádné časové zóny odpovídající '{{timezone}}' nebyly nalezeny.", | |
1970 | + "timezone-required": "Časová zóna je povinná." | |
1971 | + }, | |
1528 | 1972 | "queue": { |
1529 | 1973 | "select_name": "Vybrat název fronty", |
1530 | 1974 | "name": "Název fronty", |
... | ... | @@ -1563,6 +2007,87 @@ |
1563 | 2007 | "isolated-tb-core-details": "Vyžaduje samostatnou mikroslužbu(y) pro každého izolovaného tenanta", |
1564 | 2008 | "isolated-tb-rule-engine-details": "Vyžaduje samostatnou mikroslužbu(y) pro každého izolovaného tenanta" |
1565 | 2009 | }, |
2010 | + "tenant-profile": { | |
2011 | + "tenant-profile": "Profil tenanta", | |
2012 | + "tenant-profiles": "Profily tenantů", | |
2013 | + "add": "Přidat profil tenanta", | |
2014 | + "edit": "Editovat profil tenanta", | |
2015 | + "tenant-profile-details": "Detail profilu tenanta", | |
2016 | + "no-tenant-profiles-text": "Nebyly nalezeny žádné profily tenantů", | |
2017 | + "search": "Vyhledat profily tenantů", | |
2018 | + "selected-tenant-profiles": "Vybráno { count, plural, 1 {1 profilů tenantů} other {# profilů tenantů} }", | |
2019 | + "no-tenant-profiles-matching": "Žádné profily tenantů odpovídající '{{entity}}' nebyly nalezeny.", | |
2020 | + "tenant-profile-required": "Profil tenanta je povinný", | |
2021 | + "idCopiedMessage": "Id profilu tenanta bylo zkopírováno do schránky", | |
2022 | + "set-default": "Učinit profil tenanta defaultním", | |
2023 | + "delete": "Smazat profil tenanta", | |
2024 | + "copyId": "Kopírovat Id profilu tenanta", | |
2025 | + "name": "Název", | |
2026 | + "name-required": "Název je povinný.", | |
2027 | + "data": "Data profilu", | |
2028 | + "profile-configuration": "Konfigurace profilu", | |
2029 | + "description": "Popis", | |
2030 | + "default": "Defaultní", | |
2031 | + "delete-tenant-profile-title": "Jste si jisti, že chcete smazat profil tenanta '{{tenantProfileName}}'?", | |
2032 | + "delete-tenant-profile-text": "Buďte opatrní, protože po potvrzení nebude možné profil tenanta ani žádná související data obnovit.", | |
2033 | + "delete-tenant-profiles-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 profil tenanta} other {# profilů tenanta} }?", | |
2034 | + "delete-tenant-profiles-text": "Buďte opatrní, protože po potvrzení budou všechny vybrané profily tenantů odstraněny a žádná související data nebude možné obnovit.", | |
2035 | + "set-default-tenant-profile-title": "Jste si jisti, že chcete učinit profil tenanta '{{tenantProfileName}}' defaultním?", | |
2036 | + "set-default-tenant-profile-text": "Po potvrzení bude profil tenanta označen jako defaultní a bude použit pro nové tenanty bez specifikovaného profilu.", | |
2037 | + "no-tenant-profiles-found": "Nebyly nalezeny žádné profily tenantů.", | |
2038 | + "create-new-tenant-profile": "Vytvořit nový!", | |
2039 | + "maximum-devices": "Maximální počet zařízení (0 - neomezeno)", | |
2040 | + "maximum-devices-required": "Maximální počet zařízení je povinný.", | |
2041 | + "maximum-devices-range": "Minimální počet zařízení nemůže být záporný", | |
2042 | + "maximum-assets": "Maximální počet aktiv (0 - neomezeno)", | |
2043 | + "maximum-assets-required": "Maximální počet aktiv je povinný.", | |
2044 | + "maximum-assets-range": "Maximální počet aktiv nemůže být záporný", | |
2045 | + "maximum-customers": "Maximální počet zákazníků (0 - neomezeno)", | |
2046 | + "maximum-customers-required": "Maximální počet zákazníkůje povinný.", | |
2047 | + "maximum-customers-range": "Maximální počet zákazníků nemůže být záporný", | |
2048 | + "maximum-users": "Maximální počet uživatelů (0 - neomezeno)", | |
2049 | + "maximum-users-required": "Maximální počet uživatelů je povinný.", | |
2050 | + "maximum-users-range": "Maximální počet uživatelů nemůže být záporný", | |
2051 | + "maximum-dashboards": "Maximální počet dashboardů (0 - neomezeno)", | |
2052 | + "maximum-dashboards-required": "Maximální počet dashboardů je povinný.", | |
2053 | + "maximum-dashboards-range": "Maximální počet dashboardů nemůže být záporný", | |
2054 | + "maximum-rule-chains": "Maximální počet řetězů pravidel (0 - neomezeno)", | |
2055 | + "maximum-rule-chains-required": "Maximální počet řetězů pravidel je povinný.", | |
2056 | + "maximum-rule-chains-range": "Maximální počet řetězů pravidel nemůže být záporný", | |
2057 | + "transport-tenant-msg-rate-limit": "Limit přenosu zpráv tenanta.", | |
2058 | + "transport-tenant-telemetry-msg-rate-limit": "Limit přenosu zpráv telemetrie tenanta.", | |
2059 | + "transport-tenant-telemetry-data-points-rate-limit": "Limit přenosu datových bodů telemetrie tenanta.", | |
2060 | + "transport-device-msg-rate-limit": "Limit přenosu zpráv zařízení.", | |
2061 | + "transport-device-telemetry-msg-rate-limit": "Limit přenosu zpráv zařízení telemetrie tenanta.", | |
2062 | + "transport-device-telemetry-data-points-rate-limit": "Limit přenosu datových bodů zařízení telemetrie tenanta.", | |
2063 | + "max-transport-messages": "Maximální počet zpráv přenosu (0 - neomezeno)", | |
2064 | + "max-transport-messages-required": "Maximální počet zpráv přenosu je povinný.", | |
2065 | + "max-transport-messages-range": "Maximální počet zpráv přenosu nemůže být záporný", | |
2066 | + "max-transport-data-points": "Maximální počet datových bodů přenosu (0 - neomezeno)", | |
2067 | + "max-transport-data-points-required": "Maximální počet datových bodů přenosu je povinný.", | |
2068 | + "max-transport-data-points-range": "Maximální počet datových bodů přenosu nemůže být záporný", | |
2069 | + "max-r-e-executions": "Maximální počet zpracování enginu pro zpracování pravidel (0 - neomezeno)", | |
2070 | + "max-r-e-executions-required": "Maximální počet zpracování enginu pro zpracování pravidel je povinný.", | |
2071 | + "max-r-e-executions-range": "Maximální počet zpracování enginu pro zpracování pravidel nemůže být záporný", | |
2072 | + "max-j-s-executions": "Maximální počet JavaScript zpracování (0 - neomezeno)", | |
2073 | + "max-j-s-executions-required": "Maximální počet JavaScript zpracování je povinný.", | |
2074 | + "max-j-s-executions-range": "Maximální počet JavaScript zpracování nemůže být záporný", | |
2075 | + "max-d-p-storage-days": "Maximální počet dnů uložení datových bodů (0 - neomezeno)", | |
2076 | + "max-d-p-storage-days-required": "Maximální počet dnů uložení datových bodů je povinný.", | |
2077 | + "max-d-p-storage-days-range": "Maximální počet dnů uložení datových bodů nemůže být záporný", | |
2078 | + "default-storage-ttl-days": "Defaultní počet dnů TTL úložiště (0 - neomezeno)", | |
2079 | + "default-storage-ttl-days-required": "Defaultní počet dnů TTL úložiště je povinný.", | |
2080 | + "default-storage-ttl-days-range": "Defaultní počet dnů TTL úložiště nemůže být záporný", | |
2081 | + "max-rule-node-executions-per-message": "Maximální počet zpracování uzlů pravidel na zprávu (0 - neomezeno)", | |
2082 | + "max-rule-node-executions-per-message-required": "Maximální počet zpracování uzlů pravidel na zprávu je povinný.", | |
2083 | + "max-rule-node-executions-per-message-range": "Maximální počet zpracování uzlů pravidel na zprávu nemůže být záporný", | |
2084 | + "max-emails": "Maximální počet odeslaných emailů (0 - neomezeno)", | |
2085 | + "max-emails-required": "Maximální počet odeslaných emailů je povinný.", | |
2086 | + "max-emails-range": "Maximální počet odeslaných emailů nemůže být záporný", | |
2087 | + "max-sms": "Maximální počet odeslaných SMS (0 - neomezeno)", | |
2088 | + "max-sms-required": "Maximální počet odeslaných SMS je povinný.", | |
2089 | + "max-sms-range": "Maximální počet odeslaných SMS nemůže být záporný" | |
2090 | + }, | |
1566 | 2091 | "timeinterval": { |
1567 | 2092 | "seconds-interval": "{ seconds, plural, 1 {1 vteřina} other {# vteřin} }", |
1568 | 2093 | "minutes-interval": "{ minutes, plural, 1 {1 minuta} other {# minut} }", |
... | ... | @@ -1574,8 +2099,14 @@ |
1574 | 2099 | "seconds": "Vteřiny", |
1575 | 2100 | "advanced": "Rozšířené" |
1576 | 2101 | }, |
2102 | + "timeunit": { | |
2103 | + "seconds": "Vteřiny", | |
2104 | + "minutes": "Minuty", | |
2105 | + "hours": "Hodiny", | |
2106 | + "days": "Dny" | |
2107 | + }, | |
1577 | 2108 | "timewindow": { |
1578 | - "days": "{ days, plural, 1 { den } other {# days } }", | |
2109 | + "days": "{ days, plural, 1 { den } other {# dnů } }", | |
1579 | 2110 | "hours": "{ hours, plural, 0 { hodina } 1 {1 hodina } other {# hodin } }", |
1580 | 2111 | "minutes": "{ minutes, plural, 0 { minuta } 1 {1 minuta } other {# minut } }", |
1581 | 2112 | "seconds": "{ seconds, plural, 0 { vteřina } 1 {1 vteřina } other {# vteřin } }", |
... | ... | @@ -1694,6 +2225,7 @@ |
1694 | 2225 | "type": "Typ widgetu", |
1695 | 2226 | "resources": "Zdroje", |
1696 | 2227 | "resource-url": "JavaScript/CSS URL", |
2228 | + "resource-is-module": "Je modulem", | |
1697 | 2229 | "remove-resource": "Odebrat zdroj", |
1698 | 2230 | "add-resource": "Přidat zdroj", |
1699 | 2231 | "html": "HTML", |
... | ... | @@ -1711,7 +2243,10 @@ |
1711 | 2243 | "widget-template-load-failed-error": "Nahrání šablony widgetu selhalo!", |
1712 | 2244 | "add": "Přidat widget", |
1713 | 2245 | "undo": "Vrátit změny widgetu", |
1714 | - "export": "Exportovat widget" | |
2246 | + "export": "Exportovat widget", | |
2247 | + "no-data": "Nejsou k dispozici žádná data pro zobrazení ve widgetu", | |
2248 | + "data-overflow": "Widget zobrazuje {{count}} z {{total}} entit", | |
2249 | + "alarm-data-overflow": "Widget zobrazuje alarmy {{allowedEntities}} (maxima možných) entit z {{totalEntities}} entit" | |
1715 | 2250 | }, |
1716 | 2251 | "widget-action": { |
1717 | 2252 | "header-button": "Tlačítko hlavičky widgetu", |
... | ... | @@ -1724,7 +2259,14 @@ |
1724 | 2259 | "target-dashboard-state-required": "Cílový stav dashboardu je povinný", |
1725 | 2260 | "set-entity-from-widget": "Nastavit entitu z widgetu", |
1726 | 2261 | "target-dashboard": "Cílový dashboard", |
1727 | - "open-right-layout": "Otevřít rozmístění dashboardu vpravo (mobilní zobrazení)" | |
2262 | + "open-right-layout": "Otevřít rozmístění dashboardu vpravo (mobilní zobrazení)", | |
2263 | + "open-in-separate-dialog": "Otevřít v samostatném okně", | |
2264 | + "dialog-title": "Název okna", | |
2265 | + "dialog-hide-dashboard-toolbar": "Skrýt v okně nástrojovou lištu dashboardu", | |
2266 | + "dialog-width": "Šířka okna v procentech vzhledem k šířce obrazovky", | |
2267 | + "dialog-height": "Výška okna v procentech vzhledem k výšce obrazovky", | |
2268 | + "dialog-size-range-error": "Hodnota procentuální velikosti musí být v rozsahu od 1 do 100.", | |
2269 | + "open-new-browser-tab": "Otevřít na nové záložce prohlížeče" | |
1728 | 2270 | }, |
1729 | 2271 | "widgets-bundle": { |
1730 | 2272 | "current": "Vybraná kategorie", |
... | ... | @@ -1891,8 +2433,11 @@ |
1891 | 2433 | "entity-coordinate-required": "Obě pole, zeměpisná šířka i zeměpisná délka, jsou povinná", |
1892 | 2434 | "entity-timeseries-required": "Časové řady entity jsou povinné", |
1893 | 2435 | "get-location": "Získat aktuální polohu", |
2436 | + "invalid-date": "Neplatné datum", | |
1894 | 2437 | "latitude": "Zeměpisná šířka", |
1895 | 2438 | "longitude": "Zeměpisná délka", |
2439 | + "min-value-error": "Minimální hodnota je {{value}}", | |
2440 | + "max-value-error": "Maximální hodnota je {{value}}", | |
1896 | 2441 | "not-allowed-entity": "Vybraná entita nemůže mít sdílené atributy", |
1897 | 2442 | "no-attribute-selected": "Není vybrán žádný atribut", |
1898 | 2443 | "no-datakey-selected": "Není vybrán žádný datový klíč", |
... | ... | @@ -1900,7 +2445,10 @@ |
1900 | 2445 | "no-entity-selected": "Není vybrána žádná entita", |
1901 | 2446 | "no-image": "Žádný obrázek", |
1902 | 2447 | "no-support-geolocation": "Váš prohlížeč nepodporuje geolokaci", |
1903 | - "no-support-web-camera": "Žádná podporovaná webová kamera", | |
2448 | + "no-support-web-camera": "Váš prohlížeč nepodporuje kamery", | |
2449 | + "enable-https-use-widget": "Prosím povolte HTTPS abyste mohli používat tento widget", | |
2450 | + "no-found-your-camera": "Nelze nalézt vyši kameru", | |
2451 | + "no-permission-camera": "Přístup byl zakázán uživatelem / Tato stránka nemá oprávnění použít kameru", | |
1904 | 2452 | "no-timeseries-selected": "Nejsou vybrány žádné časové řady", |
1905 | 2453 | "secret-key": "Tajný klíč", |
1906 | 2454 | "secret-key-required": "Tajný klíč je povinný", | ... | ... |
... | ... | @@ -74,6 +74,7 @@ |
74 | 74 | "admin": { |
75 | 75 | "general": "General", |
76 | 76 | "general-settings": "General Settings", |
77 | + "home-settings": "Home Settings", | |
77 | 78 | "outgoing-mail": "Mail Server", |
78 | 79 | "outgoing-mail-settings": "Outgoing Mail Server Settings", |
79 | 80 | "system-settings": "System Settings", |
... | ... | @@ -764,7 +765,9 @@ |
764 | 765 | "select-state": "Select target state", |
765 | 766 | "state-controller": "State controller", |
766 | 767 | "search": "Search dashboards", |
767 | - "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected" | |
768 | + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected", | |
769 | + "home-dashboard": "Home dashboard", | |
770 | + "home-dashboard-hide-toolbar": "Hide home dashboard toolbar" | |
768 | 771 | }, |
769 | 772 | "datakey": { |
770 | 773 | "settings": "Settings", | ... | ... |
... | ... | @@ -4,9 +4,11 @@ |
4 | 4 | "unauthorized-access": "Acceso no autorizado", |
5 | 5 | "unauthorized-access-text": "Debes iniciar sesión para tener acceso a este recurso!", |
6 | 6 | "access-forbidden": "Acceso Prohibido", |
7 | - "access-forbidden-text": "No tienes derechos para acceder a esta ubicación!<br/>Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.", | |
7 | + "access-forbidden-text": "No tienes suficientes derechos para acceder a esta ubicación!<br/>Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.", | |
8 | 8 | "refresh-token-expired": "La sesión ha expirado", |
9 | - "refresh-token-failed": "No se puede actualizar la sesión" | |
9 | + "refresh-token-failed": "No se puede actualizar la sesión", | |
10 | + "permission-denied": "Permiso Denegado", | |
11 | + "permission-denied-text": "No tienes suficientes derechos para realizar esta operación!" | |
10 | 12 | }, |
11 | 13 | "action": { |
12 | 14 | "activate": "Activar", |
... | ... | @@ -21,6 +23,7 @@ |
21 | 23 | "no": "No", |
22 | 24 | "update": "Actualizar", |
23 | 25 | "remove": "Eliminar", |
26 | + "select": "Seleccionar", | |
24 | 27 | "search": "Buscar", |
25 | 28 | "clear-search": "Borrar búsqueda", |
26 | 29 | "assign": "Asignar", |
... | ... | @@ -49,13 +52,16 @@ |
49 | 52 | "import": "Importar", |
50 | 53 | "export": "Exportar", |
51 | 54 | "share-via": "Compartir vía {{provider}}", |
52 | - "discard-changes": "Cancelar los cambios", | |
53 | 55 | "continue": "Continuar", |
54 | - "download": "Descargar" | |
56 | + "discard-changes": "Cancelar cambios", | |
57 | + "download": "Descargar", | |
58 | + "next-with-label": "Siguiente: {{label}}", | |
59 | + "read-more": "Leer más", | |
60 | + "hide": "Ocultar" | |
55 | 61 | }, |
56 | 62 | "aggregation": { |
57 | - "aggregation": "Agregación", | |
58 | - "function": "Función de Agregación", | |
63 | + "aggregation": "Agrupación", | |
64 | + "function": "Función de Agrupación", | |
59 | 65 | "limit": "Valores Max", |
60 | 66 | "group-interval": "Intervalo de agrupación", |
61 | 67 | "min": "Min", |
... | ... | @@ -74,6 +80,8 @@ |
74 | 80 | "test-mail-sent": "Mail de prueba enviado correctamente!", |
75 | 81 | "base-url": "URL Base", |
76 | 82 | "base-url-required": "URL Base requerida.", |
83 | + "prohibit-different-url": "Prohibir el uso de hostname en cabeceras de request del cliente", | |
84 | + "prohibit-different-url-hint": "Este ajuste debe ser activado en entornos de producción. Puede causar fallos de seguridad si está desactivado", | |
77 | 85 | "mail-from": "Mail Desde", |
78 | 86 | "mail-from-required": "Mail Desde requerido.", |
79 | 87 | "smtp-protocol": "Protocolo SMTP", |
... | ... | @@ -87,9 +95,44 @@ |
87 | 95 | "timeout-invalid": "No parece un Timeout valido.", |
88 | 96 | "enable-tls": "Habilitar TLS", |
89 | 97 | "tls-version": "Versión TLS", |
98 | + "enable-proxy": "Habilitar proxy", | |
99 | + "proxy-host": "Host proxy", | |
100 | + "proxy-host-required": "Se requiere host Proxy.", | |
101 | + "proxy-port": "Puerto proxy", | |
102 | + "proxy-port-required": "Se requiere puerto proxy.", | |
103 | + "proxy-port-range": "El puerto proxy debe estar en un rango de 1 a 65535.", | |
104 | + "proxy-user": "Usuario proxy", | |
105 | + "proxy-password": "Contraseña proxy", | |
90 | 106 | "send-test-mail": "Enviar correo de prueba", |
91 | - "password-policy": "Política de contraseñas", | |
107 | + "sms-provider": "Proveedor SMS", | |
108 | + "sms-provider-settings": "Ajustes proveedor SMS", | |
109 | + "sms-provider-type": "Tipo de proveedor SMS", | |
110 | + "sms-provider-type-required": "Se requiere proveedor SMS.", | |
111 | + "sms-provider-type-aws-sns": "Amazon SNS", | |
112 | + "sms-provider-type-twilio": "Twilio", | |
113 | + "aws-access-key-id": "AWS Access Key ID", | |
114 | + "aws-access-key-id-required": "Se requiere AWS Access Key ID", | |
115 | + "aws-secret-access-key": "AWS Secret Access Key", | |
116 | + "aws-secret-access-key-required": "Se requere AWS Secret Access Key", | |
117 | + "aws-region": "Región AWS", | |
118 | + "aws-region-required": "Se requere región AWS", | |
119 | + "number-from": "Nº de teléfono Origen", | |
120 | + "number-from-required": "Se requere Nº de teléfono origen.", | |
121 | + "number-to": "Nº de teléfono de destino", | |
122 | + "number-to-required": "Se requere Nº de teléfono de destino.", | |
123 | + "phone-number-hint": "Nº de teléfono en formato E.164, ex. +19995550123", | |
124 | + "phone-number-pattern": "Nº Inválido. Debe estar en formato E.164, ex. +19995550123.", | |
125 | + "sms-message": "Mensaje SMS", | |
126 | + "sms-message-required": "Se requeriere mensaje SMS.", | |
127 | + "sms-message-max-length": "Los SMS no pueden ser más largos de 1600 caracteres", | |
128 | + "twilio-account-sid": "SID de cuenta Twilio", | |
129 | + "twilio-account-sid-required": "Se requere SID de cuenta Twilio", | |
130 | + "twilio-account-token": "Token de cuenta Twilio", | |
131 | + "twilio-account-token-required": "Se requiere Token Twilio", | |
132 | + "send-test-sms": "Enviar SMS de prueba", | |
133 | + "test-sms-sent": "SMS enviado con éxito!", | |
92 | 134 | "security-settings": "Configuraciones de seguridad", |
135 | + "password-policy": "Política de contraseñas", | |
93 | 136 | "minimum-password-length": "Longitud mínima de contraseña", |
94 | 137 | "minimum-password-length-required": "Se requiere una longitud mínima de contraseña", |
95 | 138 | "minimum-password-length-range": "La longitud mínima de la contraseña debe estar en un rango de 5 a 50", |
... | ... | @@ -108,8 +151,74 @@ |
108 | 151 | "general-policy": "Política general", |
109 | 152 | "max-failed-login-attempts": "Número máximo de intentos fallidos de inicio de sesión, antes de que la cuenta esté bloqueada", |
110 | 153 | "minimum-max-failed-login-attempts-range": "El número máximo de intentos fallidos de inicio de sesión no puede ser negativo", |
111 | - "user-lockout-notification-email": "En caso de bloqueo de la cuenta del usuario, envíe una notificación por correo electrónico" | |
112 | - }, | |
154 | + "user-lockout-notification-email": "En caso de bloqueo de la cuenta del usuario, envíe una notificación por correo electrónico", | |
155 | + "domain-name": "Nombre de dominio", | |
156 | + "domain-name-unique": "El nombre de dominio y protocolo debe ser único.", | |
157 | + "error-verification-url": "Un nombre de dominio no debe contener símbolos '/' y ':'. Ejemplo: thingsboard.io", | |
158 | + "oauth2": { | |
159 | + "access-token-uri": "URI Access token", | |
160 | + "access-token-uri-required": "Se requere URI Access token.", | |
161 | + "activate-user": "Activar usuario", | |
162 | + "add-domain": "Añadir dominio", | |
163 | + "delete-domain": "Borrar dominio", | |
164 | + "add-provider": "Añadir proveedor", | |
165 | + "delete-provider": "Borrar proveedor", | |
166 | + "allow-user-creation": "Permitir creación de usuario", | |
167 | + "always-fullscreen": "Siempre pantalla completa", | |
168 | + "authorization-uri": "URI Autorización", | |
169 | + "authorization-uri-required": "Se requiere URI de Autorización.", | |
170 | + "client-authentication-method": "Método de autenticación", | |
171 | + "client-id": "ID Cliente", | |
172 | + "client-id-required": "Se requere ID Cliente.", | |
173 | + "client-secret": "Secreto de Cliente", | |
174 | + "client-secret-required": "Se requiere Secreto de Cliente.", | |
175 | + "custom-setting": "Ajustes personalizados", | |
176 | + "customer-name-pattern": "Patrón nombre de cliente", | |
177 | + "default-dashboard-name": "Nombre de panel por defecto", | |
178 | + "delete-domain-text": "Atención, tras la confirmación el dominio y todos los datos del proveedor no estarán disponibles.", | |
179 | + "delete-domain-title": "Eliminar los ajustes del dominio '{{domainName}}'?", | |
180 | + "delete-registration-text": "Atención, tras la confirmación los datos del proveedor no estarán disponibles.", | |
181 | + "delete-registration-title": "Eliminar el proveedor '{{name}}'?", | |
182 | + "email-attribute-key": "Clave de atributos email", | |
183 | + "email-attribute-key-required": "Se requiere clave de atributos de email.", | |
184 | + "first-name-attribute-key": "Clave de atributos de nombre", | |
185 | + "general": "General", | |
186 | + "jwk-set-uri": "URI web key JSON", | |
187 | + "last-name-attribute-key": "Clave de atributos de apellido", | |
188 | + "login-button-icon": "Icono de botón login", | |
189 | + "login-button-label": "Etiqueta de proveedor", | |
190 | + "login-button-label-placeholder": "Login con $(Provider label)", | |
191 | + "login-button-label-required": "Clave de etiqueta requerida.", | |
192 | + "login-provider": "Proveedor de login", | |
193 | + "mapper": "Mapeador", | |
194 | + "new-domain": "Nuevo dominio", | |
195 | + "oauth2": "OAuth2", | |
196 | + "redirect-uri-template": "Plantilla de redirección URI", | |
197 | + "copy-redirect-uri": "Copiar URI de redirección", | |
198 | + "registration-id": "ID de registro", | |
199 | + "registration-id-required": "Se requiere ID de registro.", | |
200 | + "registration-id-unique": "El ID de registro debe ser único en el sistema.", | |
201 | + "scope": "Alcance", | |
202 | + "scope-required": "Se requiere alcance.", | |
203 | + "tenant-name-pattern": "Patrón de nombre de propietario", | |
204 | + "tenant-name-pattern-required": "Se requiere patrón de nombre de propietario.", | |
205 | + "tenant-name-strategy": "Estrategia de Nombre de Propietario", | |
206 | + "type": "Tipo de mapeador", | |
207 | + "uri-pattern-error": "Formato de URI inválido.", | |
208 | + "url": "URL", | |
209 | + "url-pattern": "Formato URL inválido.", | |
210 | + "url-required": "Se requiere URL.", | |
211 | + "user-info-uri": "URI Información de usuario", | |
212 | + "user-info-uri-required": "Se requiere URI de información usuario.", | |
213 | + "user-name-attribute-name": "Clave de atributos de nombre de usuario", | |
214 | + "user-name-attribute-name-required": "Se requiere clave de atributos de nombre de usuario", | |
215 | + "protocol": "Protocolo", | |
216 | + "domain-schema-http": "HTTP", | |
217 | + "domain-schema-https": "HTTPS", | |
218 | + "domain-schema-mixed": "HTTP+HTTPS", | |
219 | + "enable": "Activar ajustes OAuth2" | |
220 | + } | |
221 | + }, | |
113 | 222 | "alarm": { |
114 | 223 | "alarm": "Alarma", |
115 | 224 | "alarms": "Alarmas", |
... | ... | @@ -117,6 +226,8 @@ |
117 | 226 | "no-alarms-matching": "No se han encontrado alarmas coincidentes con '{{entity}}' .", |
118 | 227 | "alarm-required": "Alarma requerida", |
119 | 228 | "alarm-status": "Estado de Alarma", |
229 | + "alarm-status-list": "Lista de estados de Alarmas", | |
230 | + "any-status": "Cualquier estado", | |
120 | 231 | "search-status": { |
121 | 232 | "ANY": "Todas", |
122 | 233 | "ACTIVE": "Activas", |
... | ... | @@ -143,6 +254,8 @@ |
143 | 254 | "end-time": "Hora fin", |
144 | 255 | "ack-time": "Hora de reconocimiento", |
145 | 256 | "clear-time": "Hora de normalización", |
257 | + "alarm-severity-list": "Lista de gravedad de alarmas", | |
258 | + "any-severity": "Cualquier gravedad", | |
146 | 259 | "severity-critical": "Crítica", |
147 | 260 | "severity-major": "Mayor", |
148 | 261 | "severity-minor": "Menor", |
... | ... | @@ -158,19 +271,23 @@ |
158 | 271 | "min-polling-interval-message": "El ciclo debe ser por lo menos de 1 segundo.", |
159 | 272 | "aknowledge-alarms-title": "Reconocer { count, plural, 1 {1 alarma} other {# alarmas} }", |
160 | 273 | "aknowledge-alarms-text": "Estas seguro de reconocer { count, plural, 1 {1 alarma} other {# alarmas} }?", |
161 | - "aknowledge-alarm-title": "Recononcer Alarma", | |
274 | + "aknowledge-alarm-title": "Recononcer Alarma", | |
162 | 275 | "aknowledge-alarm-text": "Estas seguro de reconocer Alarma?", |
163 | 276 | "clear-alarms-title": "Normalizar { count, plural, 1 {1 alarma} other {# alarmas} }", |
164 | - "clear-alarms-text": "Estás seguro de limpiar { count, plural, 1 {1 alarma} other {# alarmas} }?", | |
165 | - "clear-alarm-title": "Limpiar Alarma", | |
166 | - "clear-alarm-text": "Estás seguro de limpiar Alarma?", | |
167 | - "alarm-status-filter": "Filtro de Alarmas", | |
277 | + "clear-alarms-text": "Limpiar { count, plural, 1 {1 alarma} other {# alarmas} }?", | |
278 | + "clear-alarm-title": "Limpiar Alarma", | |
279 | + "clear-alarm-text": "Limpiar Alarma?", | |
280 | + "alarm-status-filter": "Filtro de estados de Alarmas", | |
281 | + "alarm-filter": "Filtro de Alarmas", | |
168 | 282 | "max-count-load": "Número máximo de alarmas a cargar (0 - ilimitado)", |
169 | 283 | "max-count-load-required": "Se requiere número máximo de alarmas.", |
170 | 284 | "max-count-load-error-min": "El valor mínimo es 0.", |
171 | 285 | "fetch-size": "Tamaño de búsqueda (Fetch)", |
172 | 286 | "fetch-size-required": "Se requiere tamaño de búsqueda.", |
173 | - "fetch-size-error-min": "El valor mínimo es 10." | |
287 | + "fetch-size-error-min": "El valor mínimo es 10.", | |
288 | + "alarm-type-list": "Lista de tipos de alarma", | |
289 | + "any-type": "Cualquier tipo", | |
290 | + "search-propagated-alarms": "Buscar alarmas propagadas" | |
174 | 291 | }, |
175 | 292 | "alias": { |
176 | 293 | "add": "Añadir alias", |
... | ... | @@ -200,6 +317,7 @@ |
200 | 317 | "filter-type-device-search-query-description": "Dispositivos con tipos {{deviceTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", |
201 | 318 | "filter-type-entity-view-search-query": "Consulta de búsqueda de vista de entidad", |
202 | 319 | "filter-type-entity-view-search-query-description": "Vistas de entidad con tipos {{entityViewTypes}} que tienen tipo de relación {{relationType}} con dirección {{direction}} {{rootEntity}}", |
320 | + "filter-type-apiUsageState": "Uso de API", | |
203 | 321 | "entity-filter": "Filtro por entidad", |
204 | 322 | "resolve-multiple": "Tomar como múltiples entidades", |
205 | 323 | "filter-type": "Filtro por tipo", |
... | ... | @@ -259,19 +377,19 @@ |
259 | 377 | "unassign-assets": "Cancelar asignación de activo", |
260 | 378 | "unassign-assets-action-title": "Cancelar asignación de { count, plural, 1 {1 activo} other {# activos} } del cliente", |
261 | 379 | "assign-new-asset": "Asignar nuevo activo", |
262 | - "delete-asset-title": "Estás seguro de borrar el activo '{{assetName}}'?", | |
380 | + "delete-asset-title": "Eliminar el activo '{{assetName}}'?", | |
263 | 381 | "delete-asset-text": "Atención, tras la confirmación el activo y sus datos serán borrados e irrecuperables.", |
264 | - "delete-assets-title": "Estás seguro de borrar los activos { count, plural, 1 {1 activo} other {# activos} }?", | |
382 | + "delete-assets-title": "Eliminar los activos { count, plural, 1 {1 activo} other {# activos} }?", | |
265 | 383 | "delete-assets-action-title": "Borrar { count, plural, 1 {1 activo} other {# activos} }", |
266 | 384 | "delete-assets-text": "Atención, tras la confirmación todos los activos seleccionados y sus datos serán borrados e irrecuperables.", |
267 | - "make-public-asset-title": "Estás seguro de hacer el activo '{{assetName}}' público?", | |
385 | + "make-public-asset-title": "Hacer el activo '{{assetName}}' público?", | |
268 | 386 | "make-public-asset-text": "Tras la confirmación, el activo y sus datos se harán públicos y accesibles por otros.", |
269 | - "make-private-asset-title": "Estás seguro de hacer el activo '{{assetName}}' privado?", | |
387 | + "make-private-asset-title": "Hacer el activo '{{assetName}}' privado?", | |
270 | 388 | "make-private-asset-text": "Tras la confirmación, el activo y sus datos se harán privados y no serán accesibles por otros.", |
271 | - "unassign-asset-title": "Estás seguro de cancelar la asignación del activo '{{assetName}}'?", | |
389 | + "unassign-asset-title": "Cancelar la asignación del activo '{{assetName}}'?", | |
272 | 390 | "unassign-asset-text": "Tras la confirmación, el activo será desasignado y no será accesible por el cliente.", |
273 | 391 | "unassign-asset": "Cancelar asignación de activo", |
274 | - "unassign-assets-title": "Estás seguro de cancelar las asignaciones { count, plural, 1 {1 activo} other {# activos} }?", | |
392 | + "unassign-assets-title": "Cancelar las asignaciones { count, plural, 1 {1 activo} other {# activos} }?", | |
275 | 393 | "unassign-assets-text": "Tras la confirmación todos los activos seleccionados serán desasignados y no serán accesibles por el cliente.", |
276 | 394 | "copyId": "Copiar ID de activo", |
277 | 395 | "idCopiedMessage": "El ID ha sido copiado al portapapeles", |
... | ... | @@ -281,6 +399,8 @@ |
281 | 399 | "name-starts-with": "El nombre de activo comienza con", |
282 | 400 | "import": "Importar activos", |
283 | 401 | "asset-file": "Archivo del activo", |
402 | + "search": "Buscar activos", | |
403 | + "selected-assets": "{ count, plural, 1 {1 activo} other {# activos} } seleccionados", | |
284 | 404 | "label": "Etiqueta" |
285 | 405 | }, |
286 | 406 | "attribute": { |
... | ... | @@ -297,7 +417,7 @@ |
297 | 417 | "key-required": "Clave del atributo requerida.", |
298 | 418 | "value": "Valor", |
299 | 419 | "value-required": "Valor del atributo requerido.", |
300 | - "delete-attributes-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 atributo} other {# atributos} }?", | |
420 | + "delete-attributes-title": "¿Eliminar { count, plural, 1 {1 atributo} other {# atributos} }?", | |
301 | 421 | "delete-attributes-text": "Atención, tras la confirmación el atributo será eliminado, y la información relacionada será irrecuperable.", |
302 | 422 | "delete-attributes": "Borrar atributo", |
303 | 423 | "enter-attribute-value": "Ingresar valor del atributo", |
... | ... | @@ -308,7 +428,62 @@ |
308 | 428 | "add-to-dashboard": "Agregar al Panel", |
309 | 429 | "add-widget-to-dashboard": "Agregar widget al Panel", |
310 | 430 | "selected-attributes": "{ count, plural, 1 {1 atributo} other {# atributos} } seleccionados", |
311 | - "selected-telemetry": "{ count, plural, 1 {1 telemetría} other {# telemetrías} } seleccionadas" | |
431 | + "selected-telemetry": "{ count, plural, 1 {1 telemetría} other {# telemetrías} } seleccionadas", | |
432 | + "no-attributes-text": "No se encontró ningún atributo", | |
433 | + "no-telemetry-text": "No se encontró ninguna telemetría" | |
434 | + }, | |
435 | + "api-usage": { | |
436 | + "api-usage": "Uso de API", | |
437 | + "data-points": "Puntos de datos", | |
438 | + "data-points-storage-days": "Días de grabación de puntos de datos", | |
439 | + "email": "Email", | |
440 | + "email-messages": "Mensajes de Email", | |
441 | + "email-messages-daily-activity": "Actividad diaria de Emails", | |
442 | + "email-messages-hourly-activity": "Actividad horaria de Emails", | |
443 | + "email-messages-monthly-activity": "Actividad mensual de Emails", | |
444 | + "exceptions": "Excepciones", | |
445 | + "executions": "Ejecuciones", | |
446 | + "javascript": "JavaScript", | |
447 | + "javascript-executions": "Ejecuciones JavaScript", | |
448 | + "javascript-functions": "Funciones JavaScript", | |
449 | + "javascript-functions-daily-activity": "Actividad diaria de funciones JavaScript", | |
450 | + "javascript-functions-hourly-activity": "Actividad horaria de funciones JavaScript", | |
451 | + "javascript-functions-monthly-activity": "Actividad mensual de funciones JavaScript", | |
452 | + "latest-error": "Último error", | |
453 | + "messages": "Mensajes", | |
454 | + "permanent-failures": "${entityName} Fallos permanentes", | |
455 | + "permanent-timeouts": "${entityName} Timeouts permanentes", | |
456 | + "processing-failures": "${entityName} Fallos de procesamiento", | |
457 | + "processing-failures-and-timeouts": "Fallos de procesamiento y timeouts", | |
458 | + "processing-timeouts": "${entityName} Timeouts de procesamiento", | |
459 | + "queue-stats": "Estadísticas de colas", | |
460 | + "rule-chain": "Cadena de reglas", | |
461 | + "rule-engine": "Motor de reglas", | |
462 | + "rule-engine-daily-activity": "Actividad diaria de motor de reglas", | |
463 | + "rule-engine-executions": "Ejecuciones de motor de reglas", | |
464 | + "rule-engine-hourly-activity": "Actividad horaria de motor de reglas", | |
465 | + "rule-engine-monthly-activity": "Actividad mensual de motor de reglas", | |
466 | + "rule-engine-statistics": "Estadisticas del motor de reglas", | |
467 | + "rule-node": "Nodo de reglas", | |
468 | + "sms": "SMS", | |
469 | + "sms-messages": "Mensajes SMS", | |
470 | + "sms-messages-daily-activity": "Actividad diaria de mensajes SMS", | |
471 | + "sms-messages-hourly-activity": "Actividad horaria de mensajes SMS", | |
472 | + "sms-messages-monthly-activity": "Actividad mensual de mensajes SMS", | |
473 | + "successful": "${entityName} Exitoso", | |
474 | + "telemetry": "Telemetría", | |
475 | + "telemetry-persistence": "Persistencia de telemetría", | |
476 | + "telemetry-persistence-daily-activity": "Actividad diaria de persistencia de telemetría", | |
477 | + "telemetry-persistence-hourly-activity": "Actividad horaria de persistencia de telemetría", | |
478 | + "telemetry-persistence-monthly-activity": "Actividad mensual de persistencia de telemetría", | |
479 | + "transport": "Transporte", | |
480 | + "transport-daily-activity": "Actividad diaria de transporte", | |
481 | + "transport-data-points": "Puntos de datos de transporte", | |
482 | + "transport-hourly-activity": "Actividad horaria de transporte", | |
483 | + "transport-messages": "Mensajes de transporte", | |
484 | + "transport-monthly-activity": "Actividad mensual de transporte", | |
485 | + "view-details": "Ver detalles", | |
486 | + "view-statistics": "Ver estadísticas" | |
312 | 487 | }, |
313 | 488 | "audit-log": { |
314 | 489 | "audit": "Auditoría", |
... | ... | @@ -348,11 +523,17 @@ |
348 | 523 | "action-data": "Datos de acción", |
349 | 524 | "failure-details": "Detalles del error", |
350 | 525 | "search": "Buscar registros de auditoría", |
351 | - "clear-search": "Borrar búsqueda" | |
526 | + "clear-search": "Borrar búsqueda", | |
527 | + "type-assigned-from-tenant": "Asignado desde el administrador", | |
528 | + "type-assigned-to-tenant": "Asignado al administrador", | |
529 | + "type-provision-success": "Dispositivo aprovisionado", | |
530 | + "type-provision-failure": "Aprovisionamiento fallido", | |
531 | + "type-timeseries-updated": "Telemetría actualizada", | |
532 | + "type-timeseries-deleted": "Telemetría borrada" | |
352 | 533 | }, |
353 | 534 | "confirm-on-exit": { |
354 | - "message": "Tienes cambios sin guardar. ¿Estás seguro que quieres abandonar la página?", | |
355 | - "html-message": "Tienes cambios sin guardar.<br/>¿Estás seguro que quieres abandonar la página?", | |
535 | + "message": "Tienes cambios sin guardar. ¿Abandonar la página?", | |
536 | + "html-message": "Tienes cambios sin guardar.<br/>¿Abandonar la página?", | |
356 | 537 | "title": "Cambios sin guardar" |
357 | 538 | }, |
358 | 539 | "contact": { |
... | ... | @@ -372,7 +553,9 @@ |
372 | 553 | "password": "Contraseña", |
373 | 554 | "enter-username": "Introduce el nombre de usuario.", |
374 | 555 | "enter-password": "Introduce la contraseña", |
375 | - "enter-search": "Introduce búsqueda" | |
556 | + "enter-search": "Introduce búsqueda", | |
557 | + "created-time": "Fecha de creación", | |
558 | + "loading": "Cargando..." | |
376 | 559 | }, |
377 | 560 | "content-type": { |
378 | 561 | "json": "Json", |
... | ... | @@ -404,9 +587,9 @@ |
404 | 587 | "add-customer-text": "Agregar nuevo cliente", |
405 | 588 | "no-customers-text": "No se encontraron clientes", |
406 | 589 | "customer-details": "Detalles del cliente", |
407 | - "delete-customer-title": "¿Estás seguro que quieres eliminar el cliente '{{customerTitle}}'?", | |
590 | + "delete-customer-title": "¿Eliminar el cliente '{{customerTitle}}'?", | |
408 | 591 | "delete-customer-text": "Atención, tras la confirmación el cliente será eliminado y toda la información relacionada será irrecuperable.", |
409 | - "delete-customers-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 cliente} other {# clientes} }?", | |
592 | + "delete-customers-title": "¿Eliminar { count, plural, 1 {1 cliente} other {# clientes} }?", | |
410 | 593 | "delete-customers-action-title": "Borrar { count, plural, 1 {1 cliente} other {# clientes} }", |
411 | 594 | "delete-customers-text": "Atención, tras la confirmación todos los clientes seleccionados serán eliminados y su información relacionada será irrecuperable.", |
412 | 595 | "manage-users": "Gestionar usuarios", |
... | ... | @@ -425,7 +608,9 @@ |
425 | 608 | "customer-required": "Cliente requerido", |
426 | 609 | "select-default-customer": "Seleccionar cliente por defecto", |
427 | 610 | "default-customer": "Cliente por defecto", |
428 | - "default-customer-required": "Se requiere cliente por defecto para realizar debug a nivel de propietario" | |
611 | + "default-customer-required": "Se requiere cliente por defecto para realizar debu a nivel de propietario", | |
612 | + "search": "Buscar clientes", | |
613 | + "selected-customers": "{ count, plural, 1 {1 cliente} other {# clientes} } seleccionados" | |
429 | 614 | }, |
430 | 615 | "datetime": { |
431 | 616 | "date-from": "Fecha desde", |
... | ... | @@ -471,20 +656,20 @@ |
471 | 656 | "delete-dashboards": "Eliminar paneles", |
472 | 657 | "unassign-dashboards": "Desasignar paneles", |
473 | 658 | "unassign-dashboards-action-title": "Desasignar { count, plural, 1 {1 paneles} other {# paneles} } del cliente", |
474 | - "delete-dashboard-title": "¿Estás seguro que quieres eliminar el panel '{{dashboardTitle}}'?", | |
659 | + "delete-dashboard-title": "¿Eliminar el panel '{{dashboardTitle}}'?", | |
475 | 660 | "delete-dashboard-text": "Atención, el panel seleccionado será eliminado y la información relacionada sera irrecuperable.", |
476 | - "delete-dashboards-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 panel} other {# paneles} }?", | |
661 | + "delete-dashboards-title": "¿Eliminar { count, plural, 1 {1 panel} other {# paneles} }?", | |
477 | 662 | "delete-dashboards-action-title": "Eliminar { count, plural, 1 {1 panel} other {# paneles} }", |
478 | 663 | "delete-dashboards-text": "Atención, los paneles seleccionados serán eliminados y la información relacionada será irrecuperable.", |
479 | - "unassign-dashboard-title": "¿Estás seguro que quieres desasignar el panel '{{dashboardTitle}}'?", | |
664 | + "unassign-dashboard-title": "¿Desasignar el panel '{{dashboardTitle}}'?", | |
480 | 665 | "unassign-dashboard-text": "Tras la confirmación, el panel será desasignado y no podrá ser accesible por el cliente.", |
481 | 666 | "unassign-dashboard": "Desasignar panel", |
482 | - "unassign-dashboards-title": "¿Estás seguro que quieres desasignar { count, plural, 1 {1 panel} other {# paneles} }?", | |
667 | + "unassign-dashboards-title": "¿Desasignar { count, plural, 1 {1 panel} other {# paneles} }?", | |
483 | 668 | "unassign-dashboards-text": "Atención, tras la confirmación los paneles seleccionados serán desasignados y no podrán ser accesibles por el cliente.", |
484 | 669 | "public-dashboard-title": "El panel ahora es público", |
485 | 670 | "public-dashboard-text": "Tu panel <b>{{dashboardTitle}}</b> es ahora público y podrá ser accedido desde: <a href='{{publicLink}}' target='_blank'>aquí</a>:", |
486 | 671 | "public-dashboard-notice": "<b>Nota:</b> No olvides hacer públicos los dispositivos relacionados para acceder a sus datos.", |
487 | - "make-private-dashboard-title": "¿Estás seguro que quieres hacer el panel '{{dashboardTitle}}' privado?", | |
672 | + "make-private-dashboard-title": "¿Hacer el panel '{{dashboardTitle}}' privado?", | |
488 | 673 | "make-private-dashboard-text": "Tras la confirmación, el panel será privado y no podrá ser accesible por otros.", |
489 | 674 | "make-private-dashboard": "Hacer panel privado", |
490 | 675 | "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", |
... | ... | @@ -508,6 +693,9 @@ |
508 | 693 | "min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.", |
509 | 694 | "max-columns-count-message": "Solo se permite un número máximo de 1000 columnas.", |
510 | 695 | "widgets-margins": "Margen entre widgets", |
696 | + "margin-required": "Valor de margen requerido.", | |
697 | + "min-margin-message": "0 es el valor de margen mínimo permitido.", | |
698 | + "max-margin-message": "50 es el valor de margen máximo permitido.", | |
511 | 699 | "horizontal-margin": "Margen horizontal", |
512 | 700 | "horizontal-margin-required": "Margen horizontal requerido.", |
513 | 701 | "min-horizontal-margin-message": "Solo se permite margen horizontal mínimo de 0.", |
... | ... | @@ -527,6 +715,7 @@ |
527 | 715 | "title-color": "Color del título", |
528 | 716 | "display-dashboards-selection": "Mostrar selección de paneles", |
529 | 717 | "display-entities-selection": "Mostrar selección de entidades", |
718 | + "display-filters": "Mostrar filtros", | |
530 | 719 | "display-dashboard-timewindow": "Mostrar ventana de tiempo", |
531 | 720 | "display-dashboard-export": "Mostrar exportar", |
532 | 721 | "import": "Importar panel", |
... | ... | @@ -560,6 +749,7 @@ |
560 | 749 | "edit-state": "Editar estado panel", |
561 | 750 | "delete-state": "Borrar estado panel", |
562 | 751 | "add-state": "Añadir estado panel", |
752 | + "no-states-text": "No se han encontrado estados", | |
563 | 753 | "state": "Estado de panel", |
564 | 754 | "state-name": "Nombre", |
565 | 755 | "state-name-required": "Se requiere nombre del estado.", |
... | ... | @@ -568,11 +758,13 @@ |
568 | 758 | "state-id-exists": "Ya existe un ID de estado.", |
569 | 759 | "is-root-state": "Estado raiz(Root)", |
570 | 760 | "delete-state-title": "Borrar estado de panel", |
571 | - "delete-state-text": "Estás seguro de eliminar el estado de panel con nombre: '{{stateName}}'?", | |
761 | + "delete-state-text": "Eliminar el estado de panel con nombre: '{{stateName}}'?", | |
572 | 762 | "show-details": "Mostrar detalles", |
573 | 763 | "hide-details": "Ocultar detalles", |
574 | 764 | "select-state": "Seleccionar estado destino (target state)", |
575 | - "state-controller": "Controlador de estados" | |
765 | + "state-controller": "Controlador de estados", | |
766 | + "search": "Buscar paneles", | |
767 | + "selected-dashboards": "{ count, plural, 1 {1 panel} other {# paneles} } seleccionados" | |
576 | 768 | }, |
577 | 769 | "datakey": { |
578 | 770 | "settings": "Ajustes", |
... | ... | @@ -590,6 +782,7 @@ |
590 | 782 | "alarm": "Campos de alarma", |
591 | 783 | "timeseries-required": "Series de tiempo del dispositivo requerido.", |
592 | 784 | "timeseries-or-attributes-required": "Series de tiempo/Atributos requeridos.", |
785 | + "alarm-fields-timeseries-or-attributes-required": "Se requieren campos de alarma o series de tiempo/atributos.", | |
593 | 786 | "maximum-timeseries-or-attributes": "Máximo { count, plural, 1 {1 timeseries/atributo es permitido.} other {# timeseries/atributos son permitidos} }", |
594 | 787 | "alarm-fields-required": "Campos de alarma requeridos.", |
595 | 788 | "function-types": "Tipos de funciones", |
... | ... | @@ -607,6 +800,7 @@ |
607 | 800 | "add-datasource-prompt": "Por favor, agrega una fuente de datos" |
608 | 801 | }, |
609 | 802 | "details": { |
803 | + "details": "Detalles", | |
610 | 804 | "edit-mode": "Modo Edición", |
611 | 805 | "edit-json": "Editar JSON", |
612 | 806 | "toggle-edit-mode": "Ir a Modo Edición" |
... | ... | @@ -658,20 +852,20 @@ |
658 | 852 | "unassign-devices": "Desasignar dispositivos", |
659 | 853 | "unassign-devices-action-title": "Desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} } del cliente", |
660 | 854 | "assign-new-device": "Asignar nuevo dispositivo", |
661 | - "make-public-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' público?", | |
855 | + "make-public-device-title": "¿Hacer el dispositivo '{{deviceName}}' público?", | |
662 | 856 | "make-public-device-text": "Tras la confirmación, el dispositivo y la información relacionada serán públicos y podrá ser accesible por otros.", |
663 | - "make-private-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' privado?", | |
857 | + "make-private-device-title": "¿Hacer el dispositivo '{{deviceName}}' privado?", | |
664 | 858 | "make-private-device-text": "Tras la confirmación, el dispositivo y la información relacionada serán privados y no podrá ser accesible por otros.", |
665 | 859 | "view-credentials": "Ver credenciales", |
666 | - "delete-device-title": "¿Estás seguro que quieres eliminar el dispositivo '{{deviceName}}'?", | |
860 | + "delete-device-title": "¿Eliminar el dispositivo '{{deviceName}}'?", | |
667 | 861 | "delete-device-text": "Atención, tras la confirmación los dispositivos serán eliminados y la información relacionada será irrecuperable.", |
668 | - "delete-devices-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", | |
862 | + "delete-devices-title": "¿Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", | |
669 | 863 | "delete-devices-action-title": "Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }", |
670 | 864 | "delete-devices-text": "Atención, tras la confirmación los dispositivos seleccionados serán eliminados y la información relacionada será irrecuperable.", |
671 | - "unassign-device-title": "¿Estás seguro que quieres desasignar el dispositivo '{{deviceName}}'?", | |
865 | + "unassign-device-title": "¿Desasignar el dispositivo '{{deviceName}}'?", | |
672 | 866 | "unassign-device-text": "Tras la confirmación, el dispositivo será desasignado y no podrá ser accesible por el cliente.", |
673 | 867 | "unassign-device": "Desasignar dispositivo", |
674 | - "unassign-devices-title": "¿Estás seguro que quieres desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", | |
868 | + "unassign-devices-title": "¿Desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", | |
675 | 869 | "unassign-devices-text": "Tras la confirmación, los dispositivos seleccionados serán desasignados y no podrán ser accedidos por el cliente.", |
676 | 870 | "device-credentials": "Credenciales del dispositivo", |
677 | 871 | "credentials-type": "Tipo de credencial", |
... | ... | @@ -680,6 +874,12 @@ |
680 | 874 | "access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.", |
681 | 875 | "rsa-key": "Clave pública RSA", |
682 | 876 | "rsa-key-required": "Clave pública RSA requerida.", |
877 | + "client-id": "ID Cliente", | |
878 | + "client-id-pattern": "Contiene carácter inválido.", | |
879 | + "user-name": "Nombre Usuario", | |
880 | + "user-name-required": "Se requiere nombre de usuario.", | |
881 | + "client-id-or-user-name-necessary": "El ID Cliente y/o el Nombre de usuario son necesarios", | |
882 | + "password": "Contraseña", | |
683 | 883 | "secret": "Secreta", |
684 | 884 | "secret-required": "Secreta requerida.", |
685 | 885 | "device-type": "Tipo de dispositivo", |
... | ... | @@ -692,25 +892,191 @@ |
692 | 892 | "device-types": "Tipos de dispositivo", |
693 | 893 | "name": "Nombre", |
694 | 894 | "name-required": "El nombre es requerido.", |
695 | - "label": "Etiqueta", | |
696 | 895 | "description": "Descripción", |
896 | + "label": "Etiqueta", | |
697 | 897 | "events": "Eventos", |
698 | 898 | "details": "Detalles", |
699 | 899 | "copyId": "Copiar ID", |
700 | 900 | "copyAccessToken": "Copiar access token", |
901 | + "copy-mqtt-authentication": "Copiar credenciales MQTT", | |
701 | 902 | "idCopiedMessage": "Id del dispositivo copiado al portapapeles", |
702 | 903 | "accessTokenCopiedMessage": "Access token del dispositivo copiado al portapapeles", |
904 | + "mqtt-authentication-copied-message": "Los datos de autenticación MQTT se han copiado al portapapeles", | |
703 | 905 | "assignedToCustomer": "Asignado al cliente", |
704 | 906 | "unable-delete-device-alias-title": "Imposible eliminar alias del dispositivo", |
705 | 907 | "unable-delete-device-alias-text": "Alias '{{deviceAlias}}' no puede ser eliminado. Esta siendo usado por el(los) widget(s):<br/>{{widgetsList}}", |
706 | 908 | "is-gateway": "Es gateway", |
909 | + "overwrite-activity-time": "Sobreescribir hora de actividad para el dispositivo conectado", | |
707 | 910 | "public": "Público", |
708 | 911 | "device-public": "El dispositivo es público", |
709 | 912 | "select-device": "Seleccionar dispositivo", |
913 | + "import": "Importar dispositivo", | |
710 | 914 | "device-file": "Archivo de dispositivo", |
711 | - "import": "Importar dispositivo" | |
915 | + "search": "Buscar dispositivos", | |
916 | + "selected-devices": "{ count, plural, 1 {1 dispositivo} other {# dispositivos} } seleccionados", | |
917 | + "device-configuration": "Configuración del dispositivo", | |
918 | + "transport-configuration": "Configuración del transporte", | |
919 | + "wizard": { | |
920 | + "device-wizard": "Asistente de dispositivo", | |
921 | + "device-details": "Detalles del dispositivo", | |
922 | + "new-device-profile": "Crear un nuevo perfil de dispositivo", | |
923 | + "existing-device-profile": "Seleccionar un perfil existente", | |
924 | + "specific-configuration": "Configuración específica", | |
925 | + "customer-to-assign-device": "Cliente al que asignar el dispositivo", | |
926 | + "add-credential": "Añadir credencial" | |
927 | + } | |
928 | + }, | |
929 | + "device-profile": { | |
930 | + "device-profile": "Perfil de dispositivo", | |
931 | + "device-profiles": "Perfiles de dispositivo", | |
932 | + "all-device-profiles": "Todos", | |
933 | + "add": "Añadir perfil de dispositivo", | |
934 | + "edit": "Editar perfil de dispositivo", | |
935 | + "device-profile-details": "Detalles de perfil de dispositivo", | |
936 | + "no-device-profiles-text": "No se encontraron perfiles", | |
937 | + "search": "Buscar perfiles", | |
938 | + "selected-device-profiles": "{ count, plural, 1 {1 perfil} other {# perfiles} } seleccionados", | |
939 | + "no-device-profiles-matching": "No existe perfil que conincida con '{{entity}}'.", | |
940 | + "device-profile-required": "Se requiere perfil de dispositivo", | |
941 | + "idCopiedMessage": "Se ha copiado el ID de perfil al portapapeles", | |
942 | + "set-default": "Hacer perfil por defecto", | |
943 | + "delete": "Borrar perfil de dispositivo", | |
944 | + "copyId": "Copiar ID de perfil", | |
945 | + "new-device-profile-name": "Nombre de perfil", | |
946 | + "new-device-profile-name-required": "Se requiere nombre de perfil.", | |
947 | + "name": "Nombre", | |
948 | + "name-required": "Se requiere nombre.", | |
949 | + "type": "Tipo de perfil", | |
950 | + "type-required": "Se requiere tipo de perfil.", | |
951 | + "type-default": "Por defecto", | |
952 | + "transport-type": "Tipo de transporte", | |
953 | + "transport-type-required": "Se requiere tipo de transporte.", | |
954 | + "transport-type-default": "Por defecto", | |
955 | + "transport-type-default-hint": "Soporta transportes por MQTT básico, HTTP y CoAP", | |
956 | + "transport-type-mqtt": "MQTT", | |
957 | + "transport-type-mqtt-hint": "Activa ajustes avanzados de transporte MQTT", | |
958 | + "transport-type-lwm2m": "LWM2M", | |
959 | + "transport-type-lwm2m-hint": "Transporte LWM2M", | |
960 | + "description": "Descripción", | |
961 | + "default": "Defecto", | |
962 | + "profile-configuration": "Configuración de perfil", | |
963 | + "transport-configuration": "Configuración de transporte", | |
964 | + "default-rule-chain": "Cadena de reglas por defecto", | |
965 | + "select-queue-hint": "Selecciona desde el desplegable o añade un nombre personalizado.", | |
966 | + "delete-device-profile-title": "Eliminar el perfil '{{deviceProfileName}}'?", | |
967 | + "delete-device-profile-text": "Atención, tras la confirmación el perfil y todos sus datos serán borrados e irrecuperables.", | |
968 | + "delete-device-profiles-title": "EEliminar { count, plural, 1 {1 perfil} other {# perfiles} }?", | |
969 | + "delete-device-profiles-text": "Atención, tras la confirmación los perfiles seleccionados y todos sus datos serán borrados e irrecuperables.", | |
970 | + "set-default-device-profile-title": "Establecer el perfil '{{deviceProfileName}}' como perfil por defecto?", | |
971 | + "set-default-device-profile-text": "Tras la confirmación, el perfil será marcado como por defecto y será usado por todos los nuevos dispositivos que no tengan perfil especificado.", | |
972 | + "no-device-profiles-found": "No se encontraron perfiles.", | |
973 | + "create-new-device-profile": "Crear un nuevo perfil!", | |
974 | + "mqtt-device-topic-filters": "Filtros de topic MQTT", | |
975 | + "mqtt-device-topic-filters-unique": "Los filtros de topic de dispositivo MQTT deben ser únicos.", | |
976 | + "mqtt-device-payload-type": "Payload de dispositivo MQTT", | |
977 | + "mqtt-device-payload-type-json": "JSON", | |
978 | + "mqtt-device-payload-type-proto": "Protobuf", | |
979 | + "mqtt-payload-type-required": "Se requiere tipo de Payload.", | |
980 | + "support-level-wildcards": "Se soportan los wilcards únicos <code>[+]</code> y multi-nivel <code>[#]</code>.", | |
981 | + "telemetry-topic-filter": "Filtro de topic en telemetría", | |
982 | + "telemetry-topic-filter-required": "Se requiere filtro de topic (telemetría).", | |
983 | + "attributes-topic-filter": "Filtro de topic en atributos", | |
984 | + "attributes-topic-filter-required": "Se requiere filtro de topic (atributos).", | |
985 | + "telemetry-proto-schema": "Proto schema de telemetría", | |
986 | + "telemetry-proto-schema-required": "Se requiere proto schema de telemetría.", | |
987 | + "attributes-proto-schema": "Proto schema de atributos", | |
988 | + "attributes-proto-schema-required": "Se requiere proto schema de atributos.", | |
989 | + "rpc-response-topic-filter": "Filtro de topic de respuesta RPC", | |
990 | + "rpc-response-topic-filter-required": "Se requiere fitro de respuesta RPC.", | |
991 | + "not-valid-pattern-topic-filter": "No es un patrón de filtro válido", | |
992 | + "not-valid-single-character": "Uso inválido de wildcard único", | |
993 | + "not-valid-multi-character": "Uso inválido de wildcard multi-nivel", | |
994 | + "single-level-wildcards-hint": "<code>[+]</code> es adecuado para cualquier nivel. Ej.: <b>v1/devices/+/telemetry</b> o <b>+/devices/+/attributes</b>.", | |
995 | + "multi-level-wildcards-hint": "<code>[#]</code> puede reemplazar el mismo filtro y debe ser el último símbolo del topic. Ej.: <b>#</b> o <b>v1/devices/me/#</b>.", | |
996 | + "alarm-rules": "Reglas de alarma", | |
997 | + "alarm-rules-with-count": "Reglas de alarma ({{count}})", | |
998 | + "no-alarm-rules": "No hay reglas de alarma configuradas", | |
999 | + "add-alarm-rule": "Añadir regla de alarma", | |
1000 | + "edit-alarm-rule": "Editar regla de alarma", | |
1001 | + "alarm-type": "Tipo de alarma", | |
1002 | + "alarm-type-required": "Se requiere tipo de alarma.", | |
1003 | + "alarm-type-unique": "El tipo de alarma, debe ser único dentro de las reglas de alarma del perfil de dispositivo.", | |
1004 | + "create-alarm-pattern": "Crear alarma <b>{{alarmType}}</b>", | |
1005 | + "create-alarm-rules": "Crear reglas de alarma", | |
1006 | + "no-create-alarm-rules": "No hay condiciones de creación de alarma configuradas", | |
1007 | + "add-create-alarm-rule-prompt": "Por favor, añade una regla de alarma", | |
1008 | + "clear-alarm-rule": "Borrar regla de alarma", | |
1009 | + "no-clear-alarm-rule": "No hay condiciones de borrado de alarma configuradas", | |
1010 | + "add-create-alarm-rule": "Añadir crear condición (activar alarma)", | |
1011 | + "add-clear-alarm-rule": "Añair borrar condición (limpiar alarma)", | |
1012 | + "select-alarm-severity": "Selecciona severidad de alarma", | |
1013 | + "alarm-severity-required": "Se requiere especificar severidad de alarma.", | |
1014 | + "condition-duration": "Duración de condición", | |
1015 | + "condition-duration-value": "Valor de duración", | |
1016 | + "condition-duration-time-unit": "Unidad de tiempo", | |
1017 | + "condition-duration-value-range": "El valor debe estar en un rango desde 1 a 2147483647.", | |
1018 | + "condition-duration-value-pattern": "El valor de duración debe ser un número entero.", | |
1019 | + "condition-duration-value-required": "Se requiere valor de duración.", | |
1020 | + "condition-duration-time-unit-required": "Se requiere una unidad de tiempo.", | |
1021 | + "advanced-settings": "Ajustes avanzados", | |
1022 | + "alarm-rule-details": "Detalles", | |
1023 | + "add-alarm-rule-details": "Añadir detalles", | |
1024 | + "propagate-alarm": "Propagar alarma", | |
1025 | + "alarm-rule-relation-types-list": "Tipos de relación para propagar", | |
1026 | + "alarm-rule-relation-types-list-hint": "Si no está seleccionado 'propagar relaciones', las alarmas serán propagadas sin filtrar por relación.", | |
1027 | + "alarm-details": "Detalles de alarma", | |
1028 | + "alarm-rule-condition": "Condiciones de regla de alarma", | |
1029 | + "enter-alarm-rule-condition-prompt": "Por favor, añade una condición de alarma", | |
1030 | + "edit-alarm-rule-condition": "Editar condición de alarma", | |
1031 | + "device-provisioning": "Aprovisionamiento de dispositivos", | |
1032 | + "provision-strategy": "Estrategia de aprovisionamiento", | |
1033 | + "provision-strategy-required": "Se requiere estrategia de aprovisionamiento.", | |
1034 | + "provision-strategy-disabled": "Desactivado", | |
1035 | + "provision-strategy-created-new": "Permitir crear nuevos dispositivos", | |
1036 | + "provision-strategy-check-pre-provisioned": "Revisar dispositivos pre-aprovisionados", | |
1037 | + "provision-device-key": "Clave de aprovisionamiento", | |
1038 | + "provision-device-key-required": "Se requiere clave de aprovisionamiento.", | |
1039 | + "copy-provision-key": "Copiar clave de aprovisionamiento", | |
1040 | + "provision-key-copied-message": "La clave de aprovisionamiento se ha copiado al portapapeles", | |
1041 | + "provision-device-secret": "Secreto de aprovisionamiento", | |
1042 | + "provision-device-secret-required": "Se requiere secreto de aprovisionamiento.", | |
1043 | + "copy-provision-secret": "Copiar secreto de aprovisionamiento", | |
1044 | + "provision-secret-copied-message": "Se ha copiado el secreto de aprovisionamiento al portapapeles", | |
1045 | + "condition": "Condición", | |
1046 | + "condition-type": "Tipo de condición", | |
1047 | + "condition-type-simple": "Simple", | |
1048 | + "condition-type-duration": "Duración", | |
1049 | + "condition-during": "Durante {{during}}", | |
1050 | + "condition-type-repeating": "Repetitiva", | |
1051 | + "condition-type-required": "Se requiere tipo de condición.", | |
1052 | + "condition-repeating-value": "Nº de eventos", | |
1053 | + "condition-repeating-value-range": "El Nº de eventos debe estar en un rango de 1 to 2147483647.", | |
1054 | + "condition-repeating-value-pattern": "Nº de eventos debe ser un número entero.", | |
1055 | + "condition-repeating-value-required": "Se requiere Nº de eventos.", | |
1056 | + "condition-repeat-times": "Repetición { count, plural, 1 {1 vez} other {# veces} }", | |
1057 | + "schedule-type": "Tipo de horario", | |
1058 | + "schedule-type-required": "Tipo de horario requerido.", | |
1059 | + "schedule": "Horario", | |
1060 | + "edit-schedule": "Editar horario de alarma", | |
1061 | + "schedule-any-time": "Siempre activo", | |
1062 | + "schedule-specific-time": "Activo en una hora específica", | |
1063 | + "schedule-custom": "Personalizado", | |
1064 | + "schedule-day": { | |
1065 | + "monday": "Lunes", | |
1066 | + "tuesday": "Martes", | |
1067 | + "wednesday": "Miércoles", | |
1068 | + "thursday": "Jueves", | |
1069 | + "friday": "Viernes", | |
1070 | + "saturday": "Sábado", | |
1071 | + "sunday": "Domingo" | |
1072 | + }, | |
1073 | + "schedule-days": "Días", | |
1074 | + "schedule-time": "Hora", | |
1075 | + "schedule-time-from": "De", | |
1076 | + "schedule-time-to": "Hasta", | |
1077 | + "schedule-days-of-week-required": "Debe ser seleccionado por lo menos un día de la semana." | |
712 | 1078 | }, |
713 | - "dialog": { | |
1079 | + "dialog": { | |
714 | 1080 | "close": "Cerrar diálogo" |
715 | 1081 | }, |
716 | 1082 | "direction": { |
... | ... | @@ -766,6 +1132,10 @@ |
766 | 1132 | "type-devices": "Dispositivos", |
767 | 1133 | "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista de # Dispositivos} }", |
768 | 1134 | "device-name-starts-with": "Dispositivos cuyos nombres comiencen por '{{prefix}}'", |
1135 | + "type-device-profile": "Perfil de dispositivo", | |
1136 | + "type-device-profiles": "Perfiles de dispositivo", | |
1137 | + "list-of-device-profiles": "{ count, plural, 1 {un perfil} other {Lista de # perfiles} }", | |
1138 | + "device-profile-name-starts-with": "Perfiles cuyo nombre empiece por '{{prefix}}'", | |
769 | 1139 | "type-asset": "Activo", |
770 | 1140 | "type-assets": "Activos", |
771 | 1141 | "list-of-assets": "{ count, plural, 1 {Un activo} other {Lista de # activos} }", |
... | ... | @@ -785,7 +1155,11 @@ |
785 | 1155 | "type-tenant": "Propietario", |
786 | 1156 | "type-tenants": "Propietarios", |
787 | 1157 | "list-of-tenants": "{ count, plural, 1 {Un propietario} other {Lista de # propietarios} }", |
788 | - "tenant-name-starts-with": "Tenants cuyos nombres comiencen por '{{prefix}}'", | |
1158 | + "tenant-name-starts-with": "Propietarios cuyo nombre comience por '{{prefix}}'", | |
1159 | + "type-tenant-profile": "Perfil de Propietario", | |
1160 | + "type-tenant-profiles": "Perfiles de propietario", | |
1161 | + "list-of-tenant-profiles": "{ count, plural, 1 {Un perfil de propietario} other {Lista de # perfiles de propietario} }", | |
1162 | + "tenant-profile-name-starts-with": "Pefiles de propietario cuyo nombre empiece por '{{prefix}}'", | |
789 | 1163 | "type-customer": "Cliente", |
790 | 1164 | "type-customers": "Clientes", |
791 | 1165 | "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista de # clientes} }", |
... | ... | @@ -812,6 +1186,8 @@ |
812 | 1186 | "rulenode-name-starts-with": "Nodos de reglas cuyos nombres comienzan con '{{prefix}}'", |
813 | 1187 | "type-current-customer": "Cliente Actual", |
814 | 1188 | "type-current-tenant": "Propietario Actual", |
1189 | + "type-current-user": "Usuario Actual", | |
1190 | + "type-current-user-owner": "Usuario Propietario Actual", | |
815 | 1191 | "search": "Buscar entidades", |
816 | 1192 | "selected-entities": "{ count, plural, 1 {1 entidad} other {# entidades} } seleccionadas", |
817 | 1193 | "entity-label": "Etiqueta de entidad", |
... | ... | @@ -819,10 +1195,11 @@ |
819 | 1195 | "details": "Detalles de entidad", |
820 | 1196 | "no-entities-prompt": "No se han encontrado entidades", |
821 | 1197 | "no-data": "No hay datos que mostrar", |
822 | - "columns-to-display": "Columnas a Mostrar" | |
1198 | + "columns-to-display": "Columnas a Mostrar", | |
1199 | + "type-api-usage-state": "Estado de uso de la API" | |
823 | 1200 | }, |
824 | 1201 | "entity-field": { |
825 | - "created-time": "Tiempo de creación", | |
1202 | + "created-time": "Hora de creación", | |
826 | 1203 | "name": "Nombre", |
827 | 1204 | "type": "Tipo", |
828 | 1205 | "first-name": "Nombre", |
... | ... | @@ -855,6 +1232,7 @@ |
855 | 1232 | "duplicate-alias-error": "Alias duplicado'{{alias}}'.<br>Los alias de Entity View deben ser únicos en el panel.", |
856 | 1233 | "configure-alias": "Configurar alias '{{alias}}'", |
857 | 1234 | "no-entity-views-matching": "No se encontraron vistas que coincidan con '{{entity}}'.", |
1235 | + "public": "Público", | |
858 | 1236 | "alias": "Alias", |
859 | 1237 | "alias-required": "Alias de vista de entidad es requerido.", |
860 | 1238 | "remove-alias": "Borrar alias de la vista de entidad", |
... | ... | @@ -866,6 +1244,7 @@ |
866 | 1244 | "entity-view-name-filter-required": "Nombre del filtro de vista de entidad es requerido.", |
867 | 1245 | "entity-view-name-filter-no-entity-view-matched": "No se encontraron vistas de entidad que comiencen con '{{entityView}}'.", |
868 | 1246 | "add": "Añadir vista de entidad", |
1247 | + "entity-view-public": "Vista de entidad es pública", | |
869 | 1248 | "assign-to-customer": "Asignar a cliente", |
870 | 1249 | "assign-entity-view-to-customer": "Asignar vista de entidad a cliente", |
871 | 1250 | "assign-entity-view-to-customer-text": "Por favor, seleccione las vistas de entidad para asignar al cliente", |
... | ... | @@ -882,15 +1261,15 @@ |
882 | 1261 | "unassign-entity-views-action-title": "Anular asignación { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } al cliente", |
883 | 1262 | "assign-new-entity-view": "Asignar nueva vista de entidad", |
884 | 1263 | "delete-entity-view-title": "¿Está seguro que quiere borrar la vista de entidad '{{entityViewName}}'?", |
885 | - "delete-entity-view-text": "¡Cuidado! Después de la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.", | |
1264 | + "delete-entity-view-text": "¡Cuidado! Tras la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.", | |
886 | 1265 | "delete-entity-views-title": "¿Está seguro que quiere borrar las vistas de entidad { count, plural, 1 {1 entityView} other {# entityViews} }?", |
887 | 1266 | "delete-entity-views-action-title": "Borrar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }", |
888 | - "delete-entity-views-text": "¡Cuidado! Después de la confirmación, todas las vistas de entidades seleccionadas se eliminarán y todos los datos relacionados serán irrecuperables.", | |
1267 | + "delete-entity-views-text": "¡Cuidado! Tras la confirmación, todas las vistas de entidades seleccionadas se eliminarán y todos los datos relacionados serán irrecuperables.", | |
889 | 1268 | "unassign-entity-view-title": "¿Está seguro que quiere anular la asignación de la vista de entidad '{{entityViewName}}'?", |
890 | - "unassign-entity-view-text": "Después de la confirmación, la vista de la entidad quedará sin asignar y el cliente no podrá acceder a ella.", | |
1269 | + "unassign-entity-view-text": "Tras la confirmación, la vista de la entidad quedará sin asignar y el cliente no podrá acceder a ella.", | |
891 | 1270 | "unassign-entity-view": "Anular asignación de la vista de entidad", |
892 | 1271 | "unassign-entity-views-title": "¿Está seguro que quiere anular la asignación de { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }?", |
893 | - "unassign-entity-views-text": "Después de la confirmación, todas las vistas de entidades seleccionadas quedarán sin asignar y el cliente no podrá acceder a ellas.", | |
1272 | + "unassign-entity-views-text": "Tras la confirmación, todas las vistas de entidades seleccionadas quedarán sin asignar y el cliente no podrá acceder a ellas.", | |
894 | 1273 | "entity-view-type": "Tipo de vista de entidad", |
895 | 1274 | "entity-view-type-required": "Tipo de vista de entidad es requerido.", |
896 | 1275 | "select-entity-view-type": "Seleccione el tipo de vista de entidad", |
... | ... | @@ -899,12 +1278,14 @@ |
899 | 1278 | "no-entity-view-types-matching": "No se encontraron tipos de vista de entidad que coincidan con '{{entitySubtype}}'.", |
900 | 1279 | "entity-view-type-list-empty": "No hay tipos de vista de entidad seleccionados.", |
901 | 1280 | "entity-view-types": "Tipos de vista de entidad", |
1281 | + "created-time": "Fecha de creación", | |
902 | 1282 | "name": "Nombre", |
903 | 1283 | "name-required": "Nombre Requerido.", |
904 | 1284 | "description": "Descripción", |
905 | 1285 | "events": "Eventos", |
906 | 1286 | "details": "Detalles", |
907 | 1287 | "copyId": "Copiar el Id de la vista de entidad", |
1288 | + "idCopiedMessage": "El Id de la vista de entidad se ha copiado al portapapeles", | |
908 | 1289 | "assignedToCustomer": "Asignado a cliente", |
909 | 1290 | "unable-entity-view-device-alias-title": "No se puede eliminar el alias de vista de entidad", |
910 | 1291 | "unable-entity-view-device-alias-text": "El alias del dispositivo '{{entityViewAlias}}' no se puede borrar porque está siendo usado por el widget(s):<br/>{{widgetsList}}", |
... | ... | @@ -930,9 +1311,11 @@ |
930 | 1311 | "timeseries-data": "Datos de series temporales", |
931 | 1312 | "timeseries-data-hint": "Configure las claves de los datos de las series temporales de la entidad de destino que serán accesibles para la vista de la entidad. Los datos de esta serie temporal son de solo lectura.", |
932 | 1313 | "make-public-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea pública?", |
933 | - "make-public-entity-view-text": "Después de la confirmación, la vista de la entidad y todos sus datos se harán públicos y accesibles para otros.", | |
1314 | + "make-public-entity-view-text": "Tras la confirmación, la vista de la entidad y todos sus datos se harán públicos y accesibles para otros.", | |
934 | 1315 | "make-private-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea privada?", |
935 | - "make-private-entity-view-text": "Después de la confirmación, la vista de la entidad y todos sus datos se harán privados y no serán accesibles para otros." | |
1316 | + "make-private-entity-view-text": "Tras la confirmación, la vista de la entidad y todos sus datos se harán privados y no serán accesibles para otros.", | |
1317 | + "search": "Buscar vistas de entidad", | |
1318 | + "selected-entity-views": "{ count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } seleccionadas" | |
936 | 1319 | }, |
937 | 1320 | "event": { |
938 | 1321 | "event-type": "Tipo de evento", |
... | ... | @@ -965,7 +1348,7 @@ |
965 | 1348 | }, |
966 | 1349 | "extension": { |
967 | 1350 | "extensions": "Extensiones", |
968 | - "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } seleccionadas", | |
1351 | + "selected-extensions": "{ count, plural, 1 {1 extensión} other {# extensiones} } seleccionadas", | |
969 | 1352 | "type": "Tipo", |
970 | 1353 | "key": "Clave", |
971 | 1354 | "value": "Valor", |
... | ... | @@ -977,9 +1360,9 @@ |
977 | 1360 | "delete": "Borrar Extensión", |
978 | 1361 | "add": "Añadir Extensión", |
979 | 1362 | "edit": "Editar Extensión", |
980 | - "delete-extension-title": "Estás seguro de borrar la extensión '{{extensionId}}'?", | |
1363 | + "delete-extension-title": "Eliminar la extensión '{{extensionId}}'?", | |
981 | 1364 | "delete-extension-text": "Atención, tras la confirmación la extensión y sus datos serán borrados e irrecuperables.", |
982 | - "delete-extensions-title": "Estás seguro de borrar las extensiones { count, plural, 1 {1 extensión} other {# extensiones} }?", | |
1365 | + "delete-extensions-title": "Eliminar las extensiones { count, plural, 1 {1 extensión} other {# extensiones} }?", | |
983 | 1366 | "delete-extensions-text": "Atención, tras la confirmación todas las extensiones seleccionadas y sus datos serán borrados e irrecuperables.", |
984 | 1367 | "converters": "Convertidores", |
985 | 1368 | "converter-id": "Id de convertidor", |
... | ... | @@ -1120,6 +1503,93 @@ |
1120 | 1503 | "file": "Fichero de extensiones", |
1121 | 1504 | "invalid-file-error": "Fichero de extensiones inválido" |
1122 | 1505 | }, |
1506 | + "filter": { | |
1507 | + "add": "Añadir filtro", | |
1508 | + "edit": "Editar filtro", | |
1509 | + "name": "Nombre de filtro", | |
1510 | + "name-required": "Se requiere nombre de filtro.", | |
1511 | + "duplicate-filter": "Ya existe un filtro con el mismo nombre.", | |
1512 | + "filters": "Filtros", | |
1513 | + "unable-delete-filter-title": "Error borrando filtro", | |
1514 | + "unable-delete-filter-text": "El filtro '{{filter}}' no puede ser borrado debido a que está siendo usado actualmente por los siguientes widgets:<br/>{{widgetsList}}", | |
1515 | + "duplicate-filter-error": "Se ha encontrado un filtro duplicado '{{filter}}'.<br>Los filtros deben ser únicos en el panel.", | |
1516 | + "missing-key-filters-error": "No se encontró la clave de filtros para el filtro '{{filter}}'.", | |
1517 | + "filter": "Filtro", | |
1518 | + "editable": "Editable", | |
1519 | + "no-filters-found": "No se encontraron filtros.", | |
1520 | + "no-filter-text": "No se ha especificado filtro", | |
1521 | + "add-filter-prompt": "Por favos, añadir filtro", | |
1522 | + "no-filter-matching": "'{{filter}}' no encontrado.", | |
1523 | + "create-new-filter": "Crear un filtro nuevo!", | |
1524 | + "filter-required": "Se requiere filtro.", | |
1525 | + "operation": { | |
1526 | + "operation": "Operación", | |
1527 | + "equal": "igual", | |
1528 | + "not-equal": "no igual", | |
1529 | + "starts-with": "comienza con", | |
1530 | + "ends-with": "acaba con", | |
1531 | + "contains": "contiene", | |
1532 | + "not-contains": "no contiene", | |
1533 | + "greater": "mayor que", | |
1534 | + "less": "menor que", | |
1535 | + "greater-or-equal": "mayor o igual", | |
1536 | + "less-or-equal": "menor o igual", | |
1537 | + "and": "y", | |
1538 | + "or": "o" | |
1539 | + }, | |
1540 | + "ignore-case": "Ignorar mayús/minus", | |
1541 | + "value": "Valor", | |
1542 | + "remove-filter": "Borrar filtro", | |
1543 | + "preview": "Vista previa de filtro", | |
1544 | + "no-filters": "No hay filtros configurados", | |
1545 | + "add-filter": "Añadir filtro", | |
1546 | + "add-complex-filter": "Añadir filtro complejo", | |
1547 | + "add-complex": "Agregar filtro complejo", | |
1548 | + "complex-filter": "Filtro complejo", | |
1549 | + "edit-complex-filter": "Editar filtro complejo", | |
1550 | + "edit-filter-user-params": "Editar parámetros de usuario del filtro", | |
1551 | + "filter-user-params": "Filtro de parámetros de usuario (predicado)", | |
1552 | + "user-parameters": "Parámetros de usuario", | |
1553 | + "display-label": "Etiqueta a mostrar", | |
1554 | + "autogenerated-label": "Auto generar etiqueta", | |
1555 | + "order-priority": "Prioridad orden de campos", | |
1556 | + "key-filter": "Filtros (clave)", | |
1557 | + "key-filters": "Filtros (claves)", | |
1558 | + "key-name": "Nombre de clave", | |
1559 | + "key-name-required": "Se requiere nombre de clave.", | |
1560 | + "key-type": { | |
1561 | + "key-type": "Tipo de clave", | |
1562 | + "attribute": "Atributo", | |
1563 | + "timeseries": "Timeseries", | |
1564 | + "entity-field": "Campo de entidad" | |
1565 | + }, | |
1566 | + "value-type": { | |
1567 | + "value-type": "Tipo de valor", | |
1568 | + "string": "Cadena", | |
1569 | + "numeric": "Numerico", | |
1570 | + "boolean": "Booleano", | |
1571 | + "date-time": "Fecha/Hora" | |
1572 | + }, | |
1573 | + "value-type-required": "Se requiere tipo de valor.", | |
1574 | + "key-value-type-change-title": "Cambiar el tipo de valor de la clave?", | |
1575 | + "key-value-type-change-message": "Si confirmas el nuevo tipo, todos los filtros se borrarán.", | |
1576 | + "no-key-filters": "No hay filtros claves configurados", | |
1577 | + "add-key-filter": "Añadir filtro clave", | |
1578 | + "remove-key-filter": "Borrar filtro clave", | |
1579 | + "edit-key-filter": "Editar filtro clave", | |
1580 | + "date": "Fecha", | |
1581 | + "time": "Hora", | |
1582 | + "current-tenant": "Admin actual", | |
1583 | + "current-customer": "Cliente actual", | |
1584 | + "current-user": "Usuario actual", | |
1585 | + "current-device": "Dispositivo actual", | |
1586 | + "default-value": "Valor por defecto", | |
1587 | + "dynamic-source-type": "Tipo de origen dinámico", | |
1588 | + "no-dynamic-value": "Sin valor dinámico", | |
1589 | + "source-attribute": "Atributo de origen", | |
1590 | + "switch-to-dynamic-value": "Cambiar a valor dinámico", | |
1591 | + "switch-to-default-value": "Cambiar a valor por defecto" | |
1592 | + }, | |
1123 | 1593 | "fullscreen": { |
1124 | 1594 | "expand": "Expandir a Pantalla Completa", |
1125 | 1595 | "exit": "Salir de Pantalla Completa", |
... | ... | @@ -1139,7 +1609,7 @@ |
1139 | 1609 | "connector-type-required": "Se requiere tipo conector.", |
1140 | 1610 | "connectors": "Configuración de conectores", |
1141 | 1611 | "create-new-gateway": "Crear un gateway nuevo", |
1142 | - "create-new-gateway-text": "Estás seguro de crear un nuevo gateway con el nombre: '{{gatewayName}}'?", | |
1612 | + "create-new-gateway-text": "Crear un nuevo gateway con el nombre: '{{gatewayName}}'?", | |
1143 | 1613 | "delete": "Borrar configuración", |
1144 | 1614 | "download-tip": "Descargar fichero de configuración", |
1145 | 1615 | "gateway": "Gateway", |
... | ... | @@ -1201,17 +1671,12 @@ |
1201 | 1671 | "tls-path-private-key": "Ruta a la clave privada en el gateway", |
1202 | 1672 | "toggle-fullscreen": "Pantalla completa fullscreen", |
1203 | 1673 | "transformer-json-config": "Configuración JSON*", |
1204 | - "update-config": "Añadir/actualizar configuración JSON", | |
1205 | - "state-title": "Estado gateway", | |
1206 | - "show-config-tip": "Mostrar configuración gateway", | |
1207 | - "title-show-config": "Mostrar configuración gateway", | |
1208 | - "read-only": "Solo lectura", | |
1209 | - "read-write": "" | |
1674 | + "update-config": "Añadir/actualizar configuración JSON" | |
1210 | 1675 | }, |
1211 | 1676 | "grid": { |
1212 | - "delete-item-title": "¿Estás seguro que quieres eliminar este item?", | |
1677 | + "delete-item-title": "¿Quieres eliminar este item?", | |
1213 | 1678 | "delete-item-text": "Atención, tras la confirmación el item será eliminado y la información relacionada será irrecuperable.", |
1214 | - "delete-items-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 item} other {# items} }?", | |
1679 | + "delete-items-title": "¿Quieres eliminar { count, plural, 1 {1 item} other {# items} }?", | |
1215 | 1680 | "delete-items-action-title": "Eliminar { count, plural, 1 {1 item} other {# items} }", |
1216 | 1681 | "delete-items-text": "Atención, tras la confirmación los items seleccionados serán eliminados y la información relacionada será irrecuperable.", |
1217 | 1682 | "add-item-text": "Agregar nuevo item", |
... | ... | @@ -1235,10 +1700,10 @@ |
1235 | 1700 | "import": { |
1236 | 1701 | "no-file": "Ningún archivo seleccionado", |
1237 | 1702 | "drop-file": "Suelte un archivo JSON o haga clic para seleccionar un archivo para cargar.", |
1703 | + "drop-file-csv": "Suelte un archivo CSV o haga clic para seleccionar un archivo para cargar.", | |
1238 | 1704 | "column-value": "Valor", |
1239 | 1705 | "column-title": "Título", |
1240 | 1706 | "column-example": "Datos de ejemplo", |
1241 | - "drop-file-csv": "Suelte un archivo CSV o haga clic para seleccionar un archivo para cargar.", | |
1242 | 1707 | "column-key": "Clave de atributo/telemetría", |
1243 | 1708 | "csv-delimiter": "Delimitador CSV", |
1244 | 1709 | "csv-first-line-header": "La primera línea contiene nombres de columna.", |
... | ... | @@ -1257,6 +1722,7 @@ |
1257 | 1722 | "entity-field": "Campo de entidad", |
1258 | 1723 | "access-token": "Token de acceso", |
1259 | 1724 | "isgateway": "Es Gateway", |
1725 | + "activity-time-from-gateway-device": "Fecha de actividad desde el dispositivo gateway", | |
1260 | 1726 | "description": "Descripción" |
1261 | 1727 | }, |
1262 | 1728 | "stepper-text": { |
... | ... | @@ -1300,6 +1766,7 @@ |
1300 | 1766 | "legend": { |
1301 | 1767 | "direction": "Dirección de la leyenda", |
1302 | 1768 | "position": "Posición de la leyenda", |
1769 | + "sort-legend": "Ordenar claves en leyenda", | |
1303 | 1770 | "show-max": "Mostrar valor máximo", |
1304 | 1771 | "show-min": "Mostrar valor mínimo", |
1305 | 1772 | "show-avg": "Mostrar valor promedio", |
... | ... | @@ -1376,20 +1843,21 @@ |
1376 | 1843 | "any-relation-type": "Cualquier tipo", |
1377 | 1844 | "add": "Añadir relación", |
1378 | 1845 | "edit": "Editar relación", |
1379 | - "delete-to-relation-title": "¿Estás seguro que quieres eliminar la relación con la entidad '{{entityName}}'?", | |
1846 | + "delete-to-relation-title": "¿Quieres eliminar la relación con la entidad '{{entityName}}'?", | |
1380 | 1847 | "delete-to-relation-text": "Atención, tras la confirmación la entidad '{{entityName}}' no estará relacionada con la entidad actual.", |
1381 | - "delete-to-relations-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", | |
1848 | + "delete-to-relations-title": "¿Quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", | |
1382 | 1849 | "delete-to-relations-text": "Atención, tras la confirmación todas las relaciones seleccionadas se eliminarán y sus entidades correspondientes no estarán relacionadas con la entidad actual.", |
1383 | - "delete-from-relation-title": "¿Estás seguro que quieres eliminar la relación con la entidad '{{entityName}}'?", | |
1850 | + "delete-from-relation-title": "¿Quieres eliminar la relación con la entidad '{{entityName}}'?", | |
1384 | 1851 | "delete-from-relation-text": "Atención, tras la confirmación la entidad actual no estará relacionada con la entidad '{{entityName}}'.", |
1385 | - "delete-from-relations-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", | |
1852 | + "delete-from-relations-title": "¿Quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", | |
1386 | 1853 | "delete-from-relations-text": "Atención, tras la confirmación todas las relaciones seleccionadas se eliminarán y sus entidades correspondientes no estarán relacionadas con sus entidades correspondientes.", |
1387 | 1854 | "remove-relation-filter": "Quitar filtro de relación", |
1388 | 1855 | "add-relation-filter": "Añadir filtro de relación", |
1389 | 1856 | "any-relation": "Cualquier relación", |
1390 | 1857 | "relation-filters": "Filtro de relación", |
1391 | 1858 | "additional-info": "Información adicional (JSON)", |
1392 | - "invalid-additional-info": "Error al analizar el fichero JSON de información adicional." | |
1859 | + "invalid-additional-info": "Error al analizar el fichero JSON de información adicional.", | |
1860 | + "no-relations-text": "No se encontraron relaciones" | |
1393 | 1861 | }, |
1394 | 1862 | "rulechain": { |
1395 | 1863 | "rulechain": "Cadena de Regla", |
... | ... | @@ -1401,9 +1869,9 @@ |
1401 | 1869 | "description": "Descripción", |
1402 | 1870 | "add": "Añadir Cadena", |
1403 | 1871 | "set-root": "Hacer la cadena de reglas Raíz", |
1404 | - "set-root-rulechain-title": "¿Estás seguro de que desea hacer la cadena de reglas '{{ruleChainName}}' de tipo raíz?", | |
1872 | + "set-root-rulechain-title": "¿Desea hacer la cadena de reglas '{{ruleChainName}}' de tipo raíz?", | |
1405 | 1873 | "set-root-rulechain-text": "Tras la confirmación, la cadena de reglas se volverá raíz y manejará todos los mensajes de transporte entrantes.", |
1406 | - "delete-rulechain-title": "¿Estás seguro que quieres eliminar la cadena de reglas '{{ruleChainName}}'?", | |
1874 | + "delete-rulechain-title": "¿Quieres eliminar la cadena de reglas '{{ruleChainName}}'?", | |
1407 | 1875 | "delete-rulechain-text": "Atención, tras la confirmación la cadena de reglas y todos los datos serán irrecuperables.", |
1408 | 1876 | "delete-rulechains-title": "¿Está seguro que quieres eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }?", |
1409 | 1877 | "delete-rulechains-action-title": "Eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }", |
... | ... | @@ -1426,7 +1894,10 @@ |
1426 | 1894 | "no-rulechains-matching": "No se encontraron cadenas de reglas que coincidan con '{{entity}}' .", |
1427 | 1895 | "rulechain-required": "Cadena de reglas requerida", |
1428 | 1896 | "management": "Gestión de reglas", |
1429 | - "debug-mode": "Modo Debug" | |
1897 | + "debug-mode": "Modo Debug", | |
1898 | + "search": "Buscar cadenas de reglas", | |
1899 | + "selected-rulechains": "{ count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} } seleccionadas", | |
1900 | + "open-rulechain": "Abrir cadena de reglas" | |
1430 | 1901 | }, |
1431 | 1902 | "rulenode": { |
1432 | 1903 | "details": "Detalles", |
... | ... | @@ -1478,9 +1949,9 @@ |
1478 | 1949 | "type-unknown": "Desconocido", |
1479 | 1950 | "type-unknown-details": "Regla de nodo no resuelta", |
1480 | 1951 | "directive-is-not-loaded": "La directiva de configuración definida '{{directiveName}}' no está disponible.", |
1481 | - "ui-resources-load-error": "Error al cargar los recursos de configuración ui.", | |
1952 | + "ui-resources-load-error": "Error al cargar los recursos de configuración UI.", | |
1482 | 1953 | "invalid-target-rulechain": "No se puede resolver la cadena de reglas objetivo!", |
1483 | - "test-script-function": "Probar Script Función", | |
1954 | + "test-script-function": "Probar Script de función", | |
1484 | 1955 | "message": "Mensaje", |
1485 | 1956 | "message-type": "Tipo de mensaje", |
1486 | 1957 | "select-message-type": "Seleccionar tipo de mensaje", |
... | ... | @@ -1492,11 +1963,16 @@ |
1492 | 1963 | "help": "Ayuda", |
1493 | 1964 | "reset-debug-mode": "Restablecer el modo de depuración en todos los nodos" |
1494 | 1965 | }, |
1966 | + "timezone": { | |
1967 | + "timezone": "Zona Horaria", | |
1968 | + "select-timezone": "Seleccionar zona horaria", | |
1969 | + "no-timezones-matching": "No hay zonas horarias que coincidan con '{{timezone}}'.", | |
1970 | + "timezone-required": "Se requiere zona horaria." | |
1971 | + }, | |
1495 | 1972 | "queue": { |
1496 | - "select_name": "Selecciona el nombre de la cola", | |
1497 | - "name": "Nombre Cola", | |
1498 | - "name_required": "Necesario especificar el nombre de cola" | |
1499 | - | |
1973 | + "select_name": "Selecciona el nombre de la cola", | |
1974 | + "name": "Nombre Cola", | |
1975 | + "name_required": "Necesario especificar el nombre de cola" | |
1500 | 1976 | }, |
1501 | 1977 | "tenant": { |
1502 | 1978 | "tenant": "Propietario", |
... | ... | @@ -1509,9 +1985,9 @@ |
1509 | 1985 | "add-tenant-text": "Agregar nuevo propietario", |
1510 | 1986 | "no-tenants-text": "Ningún propietario encontrado", |
1511 | 1987 | "tenant-details": "Detalles del propietario", |
1512 | - "delete-tenant-title": "¿Estás seguro que quieres eliminar el propietario '{{tenantTitle}}'?", | |
1988 | + "delete-tenant-title": "¿Quieres eliminar el propietario '{{tenantTitle}}'?", | |
1513 | 1989 | "delete-tenant-text": "Atención, tras la confirmación el propietario será eliminado y la información relacionada será irrecuperable.", |
1514 | - "delete-tenants-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 propietario} other {# propietarios} }?", | |
1990 | + "delete-tenants-title": "¿Quieres eliminar { count, plural, 1 {1 propietario} other {# propietarios} }?", | |
1515 | 1991 | "delete-tenants-action-title": "Eliminar { count, plural, 1 {1 propietario} other {# propietarios} }", |
1516 | 1992 | "delete-tenants-text": "Atención, tras la confirmación los propietarios seleccionados serán eliminados y la información relacionada será irrecuperable.", |
1517 | 1993 | "title": "Título", |
... | ... | @@ -1524,21 +2000,110 @@ |
1524 | 2000 | "select-tenant": "Seleccionar propietario", |
1525 | 2001 | "no-tenants-matching": "No hay propietarios que coincidan con '{{entity}}' .", |
1526 | 2002 | "tenant-required": "Propietario requerido", |
2003 | + "search": "Buscar propietarios", | |
2004 | + "selected-tenants": "{ count, plural, 1 {1 propietario} other {# propietarios} } seleccionados", | |
1527 | 2005 | "isolated-tb-core": "Procesando en contenedor aislado", |
1528 | 2006 | "isolated-tb-rule-engine": "Procesando en contenedor Motor de Reglas aislado", |
1529 | 2007 | "isolated-tb-core-details": "Requiere microservicios separados por propietario aislado", |
1530 | 2008 | "isolated-tb-rule-engine-details": "Requiere microservicios separados por propietario aislado" |
1531 | 2009 | }, |
2010 | + "tenant-profile": { | |
2011 | + "tenant-profile": "Perfil de propietario", | |
2012 | + "tenant-profiles": "Perfiles de propietarios", | |
2013 | + "add": "Añadir perfil de propietario", | |
2014 | + "edit": "Editar perfil de propietario", | |
2015 | + "tenant-profile-details": "Detalles perfil de propietario", | |
2016 | + "no-tenant-profiles-text": "No se encontraron perfiles de propietario", | |
2017 | + "search": "Buscar perfiles de propietario", | |
2018 | + "selected-tenant-profiles": "{ count, plural, 1 {1 perfil de propietario} other {# perfiles de propietario} } seleccionados", | |
2019 | + "no-tenant-profiles-matching": "No se han encontrado perfiles de propietario que coincidan con '{{entity}}'.", | |
2020 | + "tenant-profile-required": "Se requiere perfil de propietario", | |
2021 | + "idCopiedMessage": "El ID de perfil de propietario se ha copiado al portapapeles", | |
2022 | + "set-default": "Hacer perfil propietario por defecto", | |
2023 | + "delete": "Borrar perfil", | |
2024 | + "copyId": "Copiar ID de perfil", | |
2025 | + "name": "Nombre", | |
2026 | + "name-required": "Se requiere nombre.", | |
2027 | + "data": "Datos de perfil", | |
2028 | + "profile-configuration": "Configuración de perfil", | |
2029 | + "description": "Descripción", | |
2030 | + "default": "Defecto", | |
2031 | + "delete-tenant-profile-title": "Eliminar el perfil propietario '{{tenantProfileName}}'?", | |
2032 | + "delete-tenant-profile-text": "Atención, tras la confirmación, el perfil de propietario será borrado y su información relacionada será irrecuperable.", | |
2033 | + "delete-tenant-profiles-title": "Eliminar { count, plural, 1 {1 perfil propietario} other {# perfiles propietarios} }?", | |
2034 | + "delete-tenant-profiles-text": "Atención, tras la confirmación, los perfiles seleccionados se eliminarán y su información relacionada será irrecuperable.", | |
2035 | + "set-default-tenant-profile-title": "Quieres hacer el perfil propietario '{{tenantProfileName}}' por defecto?", | |
2036 | + "set-default-tenant-profile-text": "Tras la confirmación, el perfil propietario será marcado por defecto y será usado por los nuevos perfiles propietarios que no tengan perfil específico.", | |
2037 | + "no-tenant-profiles-found": "No se encontraron perfiles de propietario.", | |
2038 | + "create-new-tenant-profile": "Crear un nuevo perfil!", | |
2039 | + "maximum-devices": "Nº Máximo de dispositivos (0 - sin límite)", | |
2040 | + "maximum-devices-required": "Nº Máximo de dispositivos requerido.", | |
2041 | + "maximum-devices-range": "Nº Máximo de dispositivos no puede ser negativo", | |
2042 | + "maximum-assets": "Nº Máximo de activos (0 - sin límite)", | |
2043 | + "maximum-assets-required": "Nº Máximo de activos requerido.", | |
2044 | + "maximum-assets-range": "Nº Máximo de activos no puede ser negativo", | |
2045 | + "maximum-customers": "Nº Máximo de clientes (0 - sin límite)", | |
2046 | + "maximum-customers-required": "Nº Máximo de clientes requerido.", | |
2047 | + "maximum-customers-range": "Nº Máximo de clientes no puede ser negativo", | |
2048 | + "maximum-users": "Nº Máximo de usuarios (0 - sin límite)", | |
2049 | + "maximum-users-required": "Nº Máximo de usuarios requerido.", | |
2050 | + "maximum-users-range": "Nº Máximo de usuarios no puede ser negativo", | |
2051 | + "maximum-dashboards": "Nº Máximo de paneles (0 - sin límite)", | |
2052 | + "maximum-dashboards-required": "Nº Máximo de paneles requerido.", | |
2053 | + "maximum-dashboards-range": "Nº Máximo de paneles no puede ser negativo", | |
2054 | + "maximum-rule-chains": "Nº Máximo de cadenas de reglas (0 - sin límite)", | |
2055 | + "maximum-rule-chains-required": "Nº Máximo de cadenas de reglas requerido.", | |
2056 | + "maximum-rule-chains-range": "Nº Máximo de cadenas de reglas no puede ser negativo", | |
2057 | + "transport-tenant-msg-rate-limit": "Tasa de mensajes de transporte por propietario.", | |
2058 | + "transport-tenant-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría por propietario.", | |
2059 | + "transport-tenant-telemetry-data-points-rate-limit": "Tasa de datapoints por propietario.", | |
2060 | + "transport-device-msg-rate-limit": "Tasa de mensajes de dispositivo.", | |
2061 | + "transport-device-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría de dispositivo.", | |
2062 | + "transport-device-telemetry-data-points-rate-limit": "Tasa de datapoints de telemetría de dispositivo.", | |
2063 | + "max-transport-messages": "Nº Máximo de mensajes de transporte (0 - sin límite)", | |
2064 | + "max-transport-messages-required": "Nº Máximo de mensajes de transporte requerido.", | |
2065 | + "max-transport-messages-range": "Nº Máximo de mensajes de transporte no puede ser negativo", | |
2066 | + "max-transport-data-points": "Nº Máximo de datapoints transporte (0 - sin límite)", | |
2067 | + "max-transport-data-points-required": "Nº Máximo de datapoints transporte requerido.", | |
2068 | + "max-transport-data-points-range": "Nº Máximo de datapoints transporte no puede ser negativo", | |
2069 | + "max-r-e-executions": "Nº Máximo de ejecuciones de motor de reglas (0 - sin límite)", | |
2070 | + "max-r-e-executions-required": "Nº Máximo de ejecuciones de motor de reglas requerido.", | |
2071 | + "max-r-e-executions-range": "Nº Máximo de ejecuciones de motor de reglas no puede ser negativo", | |
2072 | + "max-j-s-executions": "Nº Máximo de ejecuciones JavaScript (0 - sin límite)", | |
2073 | + "max-j-s-executions-required": "Nº Máximo de ejecuciones JavaScript requerido.", | |
2074 | + "max-j-s-executions-range": "Nº Máximo de ejecuciones JavaScript no puede ser negativo", | |
2075 | + "max-d-p-storage-days": "Nº Máximo de días a grabar en datapoints (0 - sin límite)", | |
2076 | + "max-d-p-storage-days-required": "Nº Máximo de días requerido.", | |
2077 | + "max-d-p-storage-days-range": "Nº Máximo de días no puede ser negativo", | |
2078 | + "default-storage-ttl-days": "Días por defecto grabado TTL (0 - sin límite)", | |
2079 | + "default-storage-ttl-days-required": "Días por defecto TTL requerido.", | |
2080 | + "default-storage-ttl-days-range": "Días por defecto TTL no puede ser negativo", | |
2081 | + "max-rule-node-executions-per-message": "Nº Máximo de ejecuciones (cadena de reglas) por mensaje (0 - sin límite)", | |
2082 | + "max-rule-node-executions-per-message-required": "Nº Máximo de ejecuciones por mensaje requerido.", | |
2083 | + "max-rule-node-executions-per-message-range": "Nº Máximo de ejecuciones por mensaje no puede ser negativo", | |
2084 | + "max-emails": "Nº Máximo de emails (0 - sin límite)", | |
2085 | + "max-emails-required": "Nº Máximo de emails requerido.", | |
2086 | + "max-emails-range": "Nº Máximo de emails no puede ser negativo", | |
2087 | + "max-sms": "Nº Máximo de mensajes SMS (0 - sin límite)", | |
2088 | + "max-sms-required": "Nº Máximo de mensajes SMS requerido.", | |
2089 | + "max-sms-range": "Nº Máximo de mensajes SMS no puede ser negativo" | |
2090 | + }, | |
1532 | 2091 | "timeinterval": { |
1533 | - "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }", | |
1534 | - "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }", | |
1535 | - "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }", | |
1536 | - "days-interval": "{ days, plural, 1 {1 día} other {# días} }", | |
1537 | - "days": "Días", | |
1538 | - "hours": "Horas", | |
1539 | - "minutes": "Minutos", | |
2092 | + "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }", | |
2093 | + "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }", | |
2094 | + "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }", | |
2095 | + "days-interval": "{ days, plural, 1 {1 día} other {# días} }", | |
2096 | + "days": "Días", | |
2097 | + "hours": "Horas", | |
2098 | + "minutes": "Minutos", | |
2099 | + "seconds": "Segundos", | |
2100 | + "advanced": "Avanzado" | |
2101 | + }, | |
2102 | + "timeunit": { | |
1540 | 2103 | "seconds": "Segundos", |
1541 | - "advanced": "Avanzado" | |
2104 | + "minutes": "Minutos", | |
2105 | + "hours": "Horas", | |
2106 | + "days": "Días" | |
1542 | 2107 | }, |
1543 | 2108 | "timewindow": { |
1544 | 2109 | "days": "{ days, plural, 1 { día } other {# días } }", |
... | ... | @@ -1569,9 +2134,9 @@ |
1569 | 2134 | "add-user-text": "Agregar nuevo usuario", |
1570 | 2135 | "no-users-text": "Ningún usuario encontrado", |
1571 | 2136 | "user-details": "Detalles del usuario", |
1572 | - "delete-user-title": "¿Estás seguro que quieres eliminar el usuario '{{userEmail}}'?", | |
2137 | + "delete-user-title": "¿Eliminar el usuario '{{userEmail}}'?", | |
1573 | 2138 | "delete-user-text": "Atención, tras la confirmación el usuario seleccionado será eliminado y la información relacionada será irrecuperable.", |
1574 | - "delete-users-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?", | |
2139 | + "delete-users-title": "¿Eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?", | |
1575 | 2140 | "delete-users-action-title": "Borrar { count, plural, 1 {1 usuario} other {# usuarios} }", |
1576 | 2141 | "delete-users-text": "Atención, tras la confirmación los usuarios seleccionados serán eliminados y la información relacionada será irrecuperable.", |
1577 | 2142 | "activation-email-sent-message": "Mail de activación enviado con éxito!", |
... | ... | @@ -1597,6 +2162,8 @@ |
1597 | 2162 | "details": "Detalles", |
1598 | 2163 | "login-as-tenant-admin": "Iniciar sesión como Administrador Propietario", |
1599 | 2164 | "login-as-customer-user": "Iniciar sesión como Usuario Cliente", |
2165 | + "search": "Buscar usuarios", | |
2166 | + "selected-users": "{ count, plural, 1 {1 usuario} other {# usuarios} } seleccionados", | |
1600 | 2167 | "disable-account": "Deshabilitar cuenta de usuario", |
1601 | 2168 | "enable-account": "Habilitar cuenta de usuario", |
1602 | 2169 | "enable-account-message": "¡La cuenta de usuario se ha habilitado correctamente!", |
... | ... | @@ -1606,18 +2173,23 @@ |
1606 | 2173 | "type": "Tipo de valor", |
1607 | 2174 | "string": "Cadena de texto", |
1608 | 2175 | "string-value": "Valor de cadena de texto", |
2176 | + "string-value-required": "Se requiere valor de cadena de texto", | |
1609 | 2177 | "integer": "Nro entero", |
1610 | 2178 | "integer-value": "Valor de nro entero", |
1611 | - "invalid-integer-value": "Valor inválido", | |
2179 | + "integer-value-required": "Se requiere valor entero", | |
2180 | + "invalid-integer-value": "Valor de entero inválido", | |
1612 | 2181 | "double": "Nro decimal", |
1613 | 2182 | "double-value": "Valor nro decimal", |
2183 | + "double-value-required": "Se requiere valor nro decimal", | |
1614 | 2184 | "boolean": "Booleano", |
1615 | 2185 | "boolean-value": "Valor booleano", |
1616 | 2186 | "false": "Falso", |
1617 | 2187 | "true": "Verdadero", |
1618 | 2188 | "long": "Nro Largo", |
1619 | 2189 | "json": "JSON", |
1620 | - "json-value": "Valor JSON" | |
2190 | + "json-value": "Valor JSON", | |
2191 | + "json-value-invalid": "El valor JSON tiene un formato inválido", | |
2192 | + "json-value-required": "Se requiere valor JSON" | |
1621 | 2193 | }, |
1622 | 2194 | "widget": { |
1623 | 2195 | "widget-library": "Bibloteca de Widgets", |
... | ... | @@ -1629,7 +2201,7 @@ |
1629 | 2201 | "widget-type-load-error": "El widget no pudo ser cargado debido a estos errores:", |
1630 | 2202 | "remove": "Eliminar widget", |
1631 | 2203 | "edit": "Editar widget", |
1632 | - "remove-widget-title": "¿Estás seguro que quieres eliminar el widget '{{widgetTitle}}'?", | |
2204 | + "remove-widget-title": "¿Eliminar el widget '{{widgetTitle}}'?", | |
1633 | 2205 | "remove-widget-text": "Atención, tras la confirmación el widget será eliminado y toda la información relacionada será irrecuperable..", |
1634 | 2206 | "timeseries": "Series de tiempo", |
1635 | 2207 | "search-data": "Buscar datos", |
... | ... | @@ -1653,6 +2225,7 @@ |
1653 | 2225 | "type": "Tipo", |
1654 | 2226 | "resources": "Recursos", |
1655 | 2227 | "resource-url": "URL JavaScript/CSS", |
2228 | + "resource-is-module": "Es módulo", | |
1656 | 2229 | "remove-resource": "Eliminar recurso", |
1657 | 2230 | "add-resource": "Agregar recurso", |
1658 | 2231 | "html": "HTML", |
... | ... | @@ -1662,7 +2235,7 @@ |
1662 | 2235 | "datakey-settings-schema": "Esquema de configuración de clave de datos", |
1663 | 2236 | "javascript": "Javascript", |
1664 | 2237 | "js": "JS", |
1665 | - "remove-widget-type-title": "¿Estás seguro que quieres eliminar el tipo del widget '{{widgetName}}'?", | |
2238 | + "remove-widget-type-title": "¿Eliminar el tipo del widget '{{widgetName}}'?", | |
1666 | 2239 | "remove-widget-type-text": "Atención, tras la confirmación el tipo será eliminado y la información relacionada será irrecuperable.", |
1667 | 2240 | "remove-widget-type": "Eliminar tipo de widget.", |
1668 | 2241 | "add-widget-type": "Agregar nuevo tipo de widget", |
... | ... | @@ -1670,7 +2243,10 @@ |
1670 | 2243 | "widget-template-load-failed-error": "Error al cargar la plantilla del widget!", |
1671 | 2244 | "add": "Agregar Widget", |
1672 | 2245 | "undo": "Deshacer cambios", |
1673 | - "export": "Exportar widget" | |
2246 | + "export": "Exportar widget", | |
2247 | + "no-data": "No hay datos para mostrar en widget", | |
2248 | + "data-overflow": "El widget muestra {{count}} de {{total}} entidades", | |
2249 | + "alarm-data-overflow": "El widget muestra alarmas para {{allowedEntities}} entidades (máximo permitido) de {{totalEntities}} entidades" | |
1674 | 2250 | }, |
1675 | 2251 | "widget-action": { |
1676 | 2252 | "header-button": "Botón de encabezado widget", |
... | ... | @@ -1683,7 +2259,14 @@ |
1683 | 2259 | "target-dashboard-state-required": "Se requiere estado de panel de destino", |
1684 | 2260 | "set-entity-from-widget": "Establecer entidad desde widget", |
1685 | 2261 | "target-dashboard": "Panel de destino", |
1686 | - "open-right-layout": "Abrir diseño de panel (derecho)(vista móvil)" | |
2262 | + "open-right-layout": "Abrir diseño de panel (derecho)(vista móvil)", | |
2263 | + "open-in-separate-dialog": "Abrir en un diálogo separado", | |
2264 | + "dialog-title": "Título del diálogo", | |
2265 | + "dialog-hide-dashboard-toolbar": "Ocultar barra de herramientas en el diálogo", | |
2266 | + "dialog-width": "Ancho de diálogo en porcentaje relativo al ancho del viewport", | |
2267 | + "dialog-height": "Alto de diálogo en porcentaje relativo al alto del viewport", | |
2268 | + "dialog-size-range-error": "El tamaño del diálogo debe ser entre un rango de 1 a 100", | |
2269 | + "open-new-browser-tab": "Abrir en una nueva pestaña" | |
1687 | 2270 | }, |
1688 | 2271 | "widgets-bundle": { |
1689 | 2272 | "current": "Paquete actual", |
... | ... | @@ -1697,9 +2280,9 @@ |
1697 | 2280 | "empty": "Paquete de widgets vacío.", |
1698 | 2281 | "details": "Detalles", |
1699 | 2282 | "widgets-bundle-details": "Detalles del paquete de Widgets", |
1700 | - "delete-widgets-bundle-title": "¿Estás seguro que quieres eliminar el paquete de widgets '{{widgetsBundleTitle}}'?", | |
2283 | + "delete-widgets-bundle-title": "¿Eliminar el paquete de widgets '{{widgetsBundleTitle}}'?", | |
1701 | 2284 | "delete-widgets-bundle-text": "Atención, tras la confirmación todos los paquetes seleccionados serán eliminados y su información relacionada será irrecuperable.", |
1702 | - "delete-widgets-bundles-title": "¿Estás seguro que deseas eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?", | |
2285 | + "delete-widgets-bundles-title": "¿Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?", | |
1703 | 2286 | "delete-widgets-bundles-action-title": "Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }", |
1704 | 2287 | "delete-widgets-bundles-text": "Atención, tras la confirmación todos los paquetes seleccionados serán eliminados y la información relacionada será irrecuperable.", |
1705 | 2288 | "no-widgets-bundles-matching": "Ningún paquete '{{widgetsBundle}}' encontrado.", |
... | ... | @@ -1710,7 +2293,10 @@ |
1710 | 2293 | "export-failed-error": "Imposible exportar paquete de widgets: {{error}}", |
1711 | 2294 | "create-new-widgets-bundle": "Crear nuevo paquete de widgets", |
1712 | 2295 | "widgets-bundle-file": "Archivo de paquete de widgets", |
1713 | - "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida." | |
2296 | + "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida.", | |
2297 | + "search": "Buscar paquete de widgets", | |
2298 | + "selected-widgets-bundles": "{ count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} } seleccionados", | |
2299 | + "open-widgets-bundle": "Abrir paquete de widgets" | |
1714 | 2300 | }, |
1715 | 2301 | "widget-config": { |
1716 | 2302 | "data": "Datos", |
... | ... | @@ -1749,6 +2335,7 @@ |
1749 | 2335 | "action": "Acción", |
1750 | 2336 | "add-action": "Añadir acción", |
1751 | 2337 | "search-actions": "Buscar acciones", |
2338 | + "no-actions-text": "No se encontraron actiones", | |
1752 | 2339 | "action-source": "Origen de acción", |
1753 | 2340 | "action-source-required": "Origen de acción requerido.", |
1754 | 2341 | "action-name": "Nombre", |
... | ... | @@ -1760,7 +2347,7 @@ |
1760 | 2347 | "edit-action": "Editar acción", |
1761 | 2348 | "delete-action": "Borrar acción", |
1762 | 2349 | "delete-action-title": "Borrar acción de widget", |
1763 | - "delete-action-text": "Estás seguro de borrar la acción de widget con el nombre '{{actionName}}'?", | |
2350 | + "delete-action-text": "Eliminar la acción de widget con el nombre '{{actionName}}'?", | |
1764 | 2351 | "display-icon": "Mostrar icono del título", |
1765 | 2352 | "icon-color": "Color del icono", |
1766 | 2353 | "icon-size": "Tamaño del icono" |
... | ... | @@ -1828,7 +2415,7 @@ |
1828 | 2415 | "Custom interval": "Intervalo personalizado", |
1829 | 2416 | "Interval": "Intervalo", |
1830 | 2417 | "Step size": "Número de pasos", |
1831 | - "Ok": "De acuerdo" | |
2418 | + "Ok": "Ok" | |
1832 | 2419 | } |
1833 | 2420 | }, |
1834 | 2421 | "input-widgets": { |
... | ... | @@ -1846,8 +2433,11 @@ |
1846 | 2433 | "entity-coordinate-required": "Se requieren ambos campos (latitud y longitud)", |
1847 | 2434 | "entity-timeseries-required": "Se requiere la serie de tiempo de la entidad", |
1848 | 2435 | "get-location": "Obtener localización actual", |
2436 | + "invalid-date": "Fecha inválida", | |
1849 | 2437 | "latitude": "Latitud", |
1850 | 2438 | "longitude": "Longitud", |
2439 | + "min-value-error": "El valor mínimo es {{value}}", | |
2440 | + "max-value-error": "El valor máximo es {{value}}", | |
1851 | 2441 | "not-allowed-entity": "La entidad seleccionada no puede tener atributos compartidos", |
1852 | 2442 | "no-attribute-selected": "No se seleccionó ningún atributo", |
1853 | 2443 | "no-datakey-selected": "No se seleccionó ninguna clave de datos", |
... | ... | @@ -1856,6 +2446,9 @@ |
1856 | 2446 | "no-image": "Sin imagen", |
1857 | 2447 | "no-support-geolocation": "Tu navegador no soporta geolocalización", |
1858 | 2448 | "no-support-web-camera": "No hay cámara web compatible", |
2449 | + "enable-https-use-widget": "Por favor, activa HTTPS para poder usar este widget", | |
2450 | + "no-found-your-camera": "No es posible encontrar la cámara", | |
2451 | + "no-permission-camera": "Permiso denegado por el usuario / Esta página no tiene permisos para usar la cámara", | |
1859 | 2452 | "no-timeseries-selected": "No hay series de tiempo seleccionadas", |
1860 | 2453 | "secret-key": "Clave", |
1861 | 2454 | "secret-key-required": "Clave requerida", | ... | ... |
... | ... | @@ -1118,6 +1118,13 @@ |
1118 | 1118 | dependencies: |
1119 | 1119 | regenerator-runtime "^0.13.4" |
1120 | 1120 | |
1121 | +"@babel/runtime@^7.12.5": | |
1122 | + version "7.12.5" | |
1123 | + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" | |
1124 | + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== | |
1125 | + dependencies: | |
1126 | + regenerator-runtime "^0.13.4" | |
1127 | + | |
1121 | 1128 | "@babel/template@7.10.4", "@babel/template@^7.10.4": |
1122 | 1129 | version "7.10.4" |
1123 | 1130 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" |
... | ... | @@ -7941,7 +7948,7 @@ rc-util@^4.15.3: |
7941 | 7948 | react-lifecycles-compat "^3.0.4" |
7942 | 7949 | shallowequal "^1.1.0" |
7943 | 7950 | |
7944 | -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: | |
7951 | +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.3.0: | |
7945 | 7952 | version "5.4.0" |
7946 | 7953 | resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" |
7947 | 7954 | 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: |
7949 | 7956 | react-is "^16.12.0" |
7950 | 7957 | shallowequal "^1.1.0" |
7951 | 7958 | |
7959 | +rc-util@^5.0.6: | |
7960 | + version "5.7.0" | |
7961 | + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.7.0.tgz#776b14cf5bbfc24f419fd40c42ffadddda0718fc" | |
7962 | + integrity sha512-0hh5XkJ+vBDeMJsHElqT1ijMx+gC3gpClwQ10h/5hccrrgrMx8VUem183KLlH1YrWCfMMPmDXWWNnwsn+p6URw== | |
7963 | + dependencies: | |
7964 | + "@babel/runtime" "^7.12.5" | |
7965 | + react-is "^16.12.0" | |
7966 | + shallowequal "^1.1.0" | |
7967 | + | |
7952 | 7968 | rc-virtual-list@^1.1.2: |
7953 | 7969 | version "1.1.6" |
7954 | 7970 | resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.6.tgz#b255baf9aacde149a8893324e6307214094f4c0a" | ... | ... |