diff --git a/application/pom.xml b/application/pom.xml index 2abfb870cb..6ba8bd6db5 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -283,7 +283,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf index c504076bb0..0ce4295535 100644 --- a/application/src/main/conf/thingsboard.conf +++ b/application/src/main/conf/thingsboard.conf @@ -15,11 +15,10 @@ # export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@/data" -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 1010013689..140004c37c 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -25,22 +25,6 @@ "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.545701115289893,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.2592906835158064,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.12880275585455747,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Bars - Chart.js\"}" } }, - { - "alias": "basic_timeseries", - "name": "Timeseries - Flot", - "descriptor": { - "type": "timeseries", - "sizeX": 8, - "sizeY": 5, - "resources": [], - "templateHtml": "", - "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", - "settingsSchema": "{}", - "dataKeySettingsSchema": "{}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" - } - }, { "alias": "doughnut_chart_js", "name": "Doughnut - Chart.js", @@ -71,7 +55,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.pie-label {\n font-size: 12px;\n font-family: 'Roboto';\n font-weight: bold;\n text-align: center;\n padding: 2px;\n color: white;\n}\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'pie'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.pieSettingsSchema();\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.pieDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\nself.actionSources = function() {\n return {\n 'sliceClick': {\n name: 'widget-action.pie-slice-click',\n multiple: false\n }\n };\n}\n", "settingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"showPercentages\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}" @@ -138,8 +122,8 @@ } }, { - "alias": "timeseries_bars_flot", - "name": "Timeseries Bars - Flot", + "alias": "state_chart", + "name": "State Chart", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -147,15 +131,15 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" } }, { - "alias": "state_chart", - "name": "State Chart", + "alias": "basic_timeseries", + "name": "Timeseries - Flot", "descriptor": { "type": "timeseries", "sizeX": 8, @@ -163,11 +147,27 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" + } + }, + { + "alias": "timeseries_bars_flot", + "name": "Timeseries Bars - Flot", + "descriptor": { + "type": "timeseries", + "sizeX": 8, + "sizeY": 5, + "resources": [], + "templateHtml": "", + "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "settingsSchema": "{}", + "dataKeySettingsSchema": "{}", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" } } ] -} +} \ No newline at end of file diff --git a/application/src/main/data/json/system/widget_bundles/navigation_widgets.json b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json new file mode 100644 index 0000000000..c37e21906c --- /dev/null +++ b/application/src/main/data/json/system/widget_bundles/navigation_widgets.json @@ -0,0 +1,41 @@ +{ + "widgetsBundle": { + "alias": "navigation_widgets", + "title": "Navigation widgets", + "image": null + }, + "widgetTypes": [ + { + "alias": "navigation_cards", + "name": "Navigation cards", + "descriptor": { + "type": "static", + "sizeX": 7, + "sizeY": 6, + "resources": [], + "templateHtml": "", + "templateCss": "/*#widget-container {\n overflow-y: auto;\n box-sizing: content-box !important;\n cursor: auto;\n}*/\n\n#widget-container #container {\n overflow-y: auto;\n box-sizing: content-box;\n cursor: auto;\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onResize = function() {\n self.ctx.$scope.navigationCardsWidget.resize();\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"filterType\": {\n \"title\": \"Filter type\",\n \"type\": \"string\",\n \"default\": \"all\"\n },\n \"filter\": {\n \"title\": \"Items\",\n \"type\": \"array\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"filterType\",\n \"type\": \"radios\",\n \"direction\": \"row\",\n \"titleMap\": [\n {\n \"value\": \"all\",\n \"name\": \"All items\"\n },\n {\n \"value\": \"include\",\n \"name\": \"Include items\"\n },\n {\n \"value\": \"exclude\",\n \"name\": \"Exclude items\"\n }\n ]\n },\n {\n \"key\": \"filter\",\n \"type\": \"rc-select\",\n \"condition\": \"model.filterType !== 'all'\",\n \"tags\": true,\n \"placeholder\": \"Enter urls to filter\",\n \"items\": [{\"value\": \"/devices\", \"label\": \"/devices\"}, {\"value\": \"/assets\", \"label\": \"/assets\"}, {\"value\": \"/deviceProfies\", \"label\": \"/deviceProfies\"}]\n }\n ]\n}\n", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(255,255,255,0)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"filterType\":\"all\"},\"title\":\"Navigation cards\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" + } + }, + { + "alias": "navigation_card", + "name": "Navigation card", + "descriptor": { + "type": "static", + "sizeX": 2.5, + "sizeY": 2, + "resources": [], + "templateHtml": "", + "templateCss": "", + "controllerScript": "self.onInit = function() {\n\n}\n\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"name\": {\n \"title\": \"Title\",\n \"type\": \"string\",\n \"default\": \"{i18n:device.devices}\"\n },\n \"icon\": {\n \"title\": \"icon\",\n \"type\": \"string\",\n \"default\": \"devices_other\"\n },\n \"path\": {\n \"title\": \"Navigation path\",\n \"type\": \"string\",\n \"default\": \"/devices\"\n }\n },\n \"required\": [\"name\", \"icon\", \"path\"]\n },\n \"form\": [\n \"name\",\n {\n \"key\": \"icon\",\n \"type\": \"icon\"\n },\n \"path\"\n ]\n}\n", + "dataKeySettingsSchema": "{}\n", + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgba(255,255,255,0)\",\"color\":\"rgba(255,255,255,0.87)\",\"padding\":\"8px\",\"settings\":{\"name\":\"{i18n:device.devices}\",\"icon\":\"devices_other\",\"path\":\"/devices\"},\"title\":\"Navigation card\",\"dropShadow\":false,\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"showLegend\":false}" + } + } + ] +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 173c4cfd1a..81e9c8917a 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -141,12 +141,12 @@ public class AppActor extends ContextAwareActor { private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { - msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!")); + msg.getMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!")); } else { if (!deletedTenants.contains(msg.getTenantId())) { getOrCreateTenantActor(msg.getTenantId()).tell(msg); } else { - msg.getTbMsg().getCallback().onSuccess(); + msg.getMsg().getCallback().onSuccess(); } } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 73b8613f9f..34252cd7d7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.StringUtils; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; @@ -92,10 +91,12 @@ class DefaultTbContext implements TbContext { public final static ObjectMapper mapper = new ObjectMapper(); private final ActorSystemContext mainCtx; + private final String ruleChainName; private final RuleNodeCtx nodeCtx; - public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) { + public DefaultTbContext(ActorSystemContext mainCtx, String ruleChainName, RuleNodeCtx nodeCtx) { this.mainCtx = mainCtx; + this.ruleChainName = ruleChainName; this.nodeCtx = nodeCtx; } @@ -119,13 +120,13 @@ class DefaultTbContext implements TbContext { relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th)); } msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId()); - nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null)); + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null)); } @Override public void tellSelf(TbMsg msg, long delayMs) { //TODO: add persistence layer - scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor()); + scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor()); } @Override @@ -256,7 +257,8 @@ class DefaultTbContext implements TbContext { } else { failureMessage = null; } - nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), + nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getRuleChainId(), + nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), msg, failureMessage)); } @@ -308,6 +310,16 @@ class DefaultTbContext implements TbContext { return nodeCtx.getSelf().getId(); } + @Override + public RuleNode getSelf() { + return nodeCtx.getSelf(); + } + + @Override + public String getRuleChainName() { + return ruleChainName; + } + @Override public TenantId getTenantId() { return nodeCtx.getTenantId(); @@ -492,11 +504,6 @@ class DefaultTbContext implements TbContext { return mainCtx.getCassandraBufferedRateExecutor().submit(task); } - @Override - public RedisTemplate getRedisTemplate() { - return mainCtx.getRedisTemplate(); - } - @Override public PageData findRuleNodeStates(PageLink pageLink) { if (log.isDebugEnabled()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 0ce851edcb..10d6272beb 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -23,7 +23,6 @@ import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; -import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.common.msg.plugin.RuleNodeUpdatedMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; @@ -133,7 +133,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor componentClazz = Class.forName(ruleNode.getType()); - tbNode = (TbNode) (componentClazz.newInstance()); + tbNode = (TbNode) (componentClazz.getDeclaredConstructor().newInstance()); tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration())); } return tbNode; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java index 32e1a7536b..f7a26403aa 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java @@ -15,11 +15,16 @@ */ package org.thingsboard.server.actors.ruleChain; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg; +import org.thingsboard.server.common.msg.queue.RuleEngineException; import java.io.Serializable; import java.util.Set; @@ -27,15 +32,34 @@ import java.util.Set; /** * Created by ashvayka on 19.03.18. */ -@Data -class RuleNodeToRuleChainTellNextMsg implements TbActorMsg, Serializable { +@EqualsAndHashCode(callSuper = true) +@ToString +class RuleNodeToRuleChainTellNextMsg extends TbRuleEngineActorMsg implements Serializable { private static final long serialVersionUID = 4577026446412871820L; + @Getter + private final RuleChainId ruleChainId; + @Getter private final RuleNodeId originator; + @Getter private final Set relationTypes; - private final TbMsg msg; + @Getter private final String failureMessage; + public RuleNodeToRuleChainTellNextMsg(RuleChainId ruleChainId, RuleNodeId originator, Set relationTypes, TbMsg tbMsg, String failureMessage) { + super(tbMsg); + this.ruleChainId = ruleChainId; + this.originator = originator; + this.relationTypes = relationTypes; + this.failureMessage = failureMessage; + } + + @Override + public void onTbActorStopped(TbActorStopReason reason) { + String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", ruleChainId.getId()) : String.format("Failed to initialize rule chain [%s]!", ruleChainId.getId()); + msg.getCallback().onFailure(new RuleEngineException(message)); + } + @Override public MsgType getMsgType() { return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java index bdd33f2381..3ebaa7ffa8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java @@ -15,18 +15,25 @@ */ package org.thingsboard.server.actors.ruleChain; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg; +import org.thingsboard.server.common.msg.queue.RuleNodeException; /** * Created by ashvayka on 19.03.18. */ -@Data -final class RuleNodeToSelfMsg implements TbActorMsg { +@EqualsAndHashCode(callSuper = true) +@ToString +final class RuleNodeToSelfMsg extends TbToRuleNodeActorMsg { - private final TbMsg msg; + public RuleNodeToSelfMsg(TbContext ctx, TbMsg tbMsg) { + super(ctx, tbMsg); + } @Override public MsgType getMsgType() { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleNodeActorMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleNodeActorMsg.java new file mode 100644 index 0000000000..6d492f630a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleNodeActorMsg.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors.ruleChain; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.msg.TbActorStopReason; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg; +import org.thingsboard.server.common.msg.queue.RuleNodeException; + +@EqualsAndHashCode(callSuper = true) +public abstract class TbToRuleNodeActorMsg extends TbRuleEngineActorMsg { + + @Getter + private final TbContext ctx; + + public TbToRuleNodeActorMsg(TbContext ctx, TbMsg tbMsg) { + super(tbMsg); + this.ctx = ctx; + } + + @Override + public void onTbActorStopped(TbActorStopReason reason) { + String message = reason == TbActorStopReason.STOPPED ? "Rule node stopped" : "Failed to initialize rule node!"; + msg.getCallback().onFailure(new RuleNodeException(message, ctx.getRuleChainName(), ctx.getSelf())); + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java index 05dc056715..e356209532 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java @@ -20,6 +20,7 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorException; +import org.thingsboard.server.actors.TbRuleNodeUpdateException; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; import org.thingsboard.server.actors.stats.StatsPersistMsg; import org.thingsboard.server.common.data.id.EntityId; @@ -123,6 +124,9 @@ public abstract class ComponentActor implements ActorService { public static final String APP_DISPATCHER_NAME = "app-dispatcher"; public static final String TENANT_DISPATCHER_NAME = "tenant-dispatcher"; @@ -120,10 +121,10 @@ public class DefaultActorService implements ActorService { appActor.tellWithHighPriority(new AppInitMsg()); } - @EventListener(PartitionChangeEvent.class) - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + @Override + protected void onTbApplicationEvent(PartitionChangeEvent event) { log.info("Received partition change event."); - this.appActor.tellWithHighPriority(new PartitionChangeMsg(partitionChangeEvent.getServiceQueueKey(), partitionChangeEvent.getPartitions())); + this.appActor.tellWithHighPriority(new PartitionChangeMsg(event.getServiceQueueKey(), event.getPartitions())); } @PreDestroy diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java index 5e6f69278d..1f64c71629 100644 --- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java @@ -28,10 +28,10 @@ import org.thingsboard.server.common.msg.TbActorMsg; @ToString public final class StatsPersistMsg implements TbActorMsg { - private long messagesProcessed; - private long errorsOccurred; - private TenantId tenantId; - private EntityId entityId; + private final long messagesProcessed; + private final long errorsOccurred; + private final TenantId tenantId; + private final EntityId entityId; @Override public MsgType getMsgType() { diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java index f8edccf692..012e26e216 100644 --- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistTick.java @@ -18,7 +18,7 @@ package org.thingsboard.server.actors.stats; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; -public final class StatsPersistTick implements TbActorMsg{ +public final class StatsPersistTick implements TbActorMsg { @Override public MsgType getMsgType() { return MsgType.STATS_PERSIST_TICK_MSG; diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 40e96cd9ea..68afbdaf99 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -125,7 +125,7 @@ public class TenantActor extends RuleChainManagerActor { log.info("[{}] Processing missing Tenant msg: {}", tenantId, msg); if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) { QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg; - queueMsg.getTbMsg().getCallback().onSuccess(); + queueMsg.getMsg().getCallback().onSuccess(); } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)) { TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg; transportMsg.getCallback().onSuccess(); @@ -188,7 +188,7 @@ public class TenantActor extends RuleChainManagerActor { log.warn("RECEIVED INVALID MESSAGE: {}", msg); return; } - TbMsg tbMsg = msg.getTbMsg(); + TbMsg tbMsg = msg.getMsg(); if (apiUsageState.isReExecEnabled()) { if (tbMsg.getRuleChainId() == null) { if (getRootChainActor() != null) { diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index 0fcaf90525..bbcb7d656a 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -91,6 +91,7 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza return action; } + @SuppressWarnings("deprecation") private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { if (registrationId == null) { return null; diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index d89fbf94c8..86fb136385 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -127,7 +127,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt } protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { - List pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); + List pathsToSkip = new ArrayList<>(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT)); SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index c168a3aef4..590138dbd4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -152,6 +152,9 @@ public abstract class BaseController { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!"; + protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; + protected static final String HOME_DASHBOARD = "homeDashboardId"; + private static final ObjectMapper json = new ObjectMapper(); @Autowired @@ -719,6 +722,7 @@ public abstract class BaseController { return ruleNode; } + @SuppressWarnings("unchecked") protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } @@ -849,8 +853,9 @@ public abstract class BaseController { entityNode = json.createObjectNode(); if (actionType == ActionType.ATTRIBUTES_UPDATED) { String scope = extractParameter(String.class, 0, additionalInfo); + @SuppressWarnings("unchecked") List attributes = extractParameter(List.class, 1, additionalInfo); - metaData.putValue("scope", scope); + metaData.putValue(DataConstants.SCOPE, scope); if (attributes != null) { for (AttributeKvEntry attr : attributes) { addKvEntry(entityNode, attr); @@ -858,16 +863,19 @@ public abstract class BaseController { } } else if (actionType == ActionType.ATTRIBUTES_DELETED) { String scope = extractParameter(String.class, 0, additionalInfo); + @SuppressWarnings("unchecked") List keys = extractParameter(List.class, 1, additionalInfo); - metaData.putValue("scope", scope); + metaData.putValue(DataConstants.SCOPE, scope); ArrayNode attrsArrayNode = entityNode.putArray("attributes"); if (keys != null) { keys.forEach(attrsArrayNode::add); } } else if (actionType == ActionType.TIMESERIES_UPDATED) { + @SuppressWarnings("unchecked") List timeseries = extractParameter(List.class, 0, additionalInfo); addTimeseries(entityNode, timeseries); } else if (actionType == ActionType.TIMESERIES_DELETED) { + @SuppressWarnings("unchecked") List keys = extractParameter(List.class, 0, additionalInfo); if (keys != null) { ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries"); @@ -1030,4 +1038,14 @@ public abstract class BaseController { } } } + + protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { + String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; + if(dashboardId != null && !dashboardId.equals("null")) { + if(dashboardService.findDashboardById(getTenantId(), new DashboardId(UUID.fromString(dashboardId))) == null) { + additionalInfo.remove(requiredFields); + } + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 5c6fcae2f5..484b15f076 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -59,7 +59,11 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); try { CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - return checkCustomerId(customerId, Operation.READ); + Customer customer = checkCustomerId(customerId, Operation.READ); + if(!customer.getAdditionalInfo().isNull()) { + processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD); + } + return customer; } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 016a4423e7..ee4b075f44 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; @@ -30,7 +32,11 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HomeDashboard; +import org.thingsboard.server.common.data.HomeDashboardInfo; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -42,7 +48,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -58,6 +66,9 @@ public class DashboardController extends BaseController { public static final String DASHBOARD_ID = "dashboardId"; + private static final String HOME_DASHBOARD_ID = "homeDashboardId"; + private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; + @Value("${dashboard.max_datapoints_limit}") private long maxDatapointsLimit; @@ -491,6 +502,102 @@ public class DashboardController extends BaseController { } } + @PreAuthorize("isAuthenticated()") + @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) + @ResponseBody + public HomeDashboard getHomeDashboard() throws ThingsboardException { + try { + SecurityUser securityUser = getCurrentUser(); + if (securityUser.isSystemAdmin()) { + return null; + } + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + JsonNode additionalInfo = user.getAdditionalInfo(); + HomeDashboard homeDashboard; + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + if (homeDashboard == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + additionalInfo = customer.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + } + if (homeDashboard == null) { + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + additionalInfo = tenant.getAdditionalInfo(); + homeDashboard = extractHomeDashboardFromAdditionalInfo(additionalInfo); + } + } + return homeDashboard; + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) + @ResponseBody + public HomeDashboardInfo getTenantHomeDashboardInfo() throws ThingsboardException { + try { + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + DashboardId dashboardId = null; + boolean hideDashboardToolbar = true; + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + dashboardId = new DashboardId(toUUID(strDashboardId)); + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + } + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) + @ResponseStatus(value = HttpStatus.OK) + public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { + try { + if (homeDashboardInfo.getDashboardId() != null) { + checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); + } + Tenant tenant = tenantService.findTenantById(getTenantId()); + JsonNode additionalInfo = tenant.getAdditionalInfo(); + if (additionalInfo == null || !(additionalInfo instanceof ObjectNode)) { + additionalInfo = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + } + if (homeDashboardInfo.getDashboardId() != null) { + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_ID, homeDashboardInfo.getDashboardId().getId().toString()); + ((ObjectNode) additionalInfo).put(HOME_DASHBOARD_HIDE_TOOLBAR, homeDashboardInfo.isHideDashboardToolbar()); + } else { + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_ID); + ((ObjectNode) additionalInfo).remove(HOME_DASHBOARD_HIDE_TOOLBAR); + } + tenant.setAdditionalInfo(additionalInfo); + tenantService.saveTenant(tenant); + } catch (Exception e) { + throw handleException(e); + } + } + + private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { + try { + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.READ); + boolean hideDashboardToolbar = true; + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + return new HomeDashboard(dashboard, hideDashboardToolbar); + } + } catch (Exception e) {} + return null; + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index ba424dfdf0..5e9103a66a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -73,7 +73,7 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; import static org.thingsboard.server.controller.EdgeController.EDGE_ID; diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index d05cf2179a..7621d05908 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; @@ -59,7 +60,11 @@ public class TenantController extends BaseController { checkParameter("tenantId", strTenantId); try { TenantId tenantId = new TenantId(toUUID(strTenantId)); - return checkTenantId(tenantId, Operation.READ); + Tenant tenant = checkTenantId(tenantId, Operation.READ); + if(!tenant.getAdditionalInfo().isNull()) { + processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD); + } + return tenant; } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 393ca46779..2a1ec5810e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -92,7 +92,12 @@ public class UserController extends BaseController { checkParameter(USER_ID, strUserId); try { UserId userId = new UserId(toUUID(strUserId)); - return checkUserId(userId, Operation.READ); + User user = checkUserId(userId, Operation.READ); + if(!user.getAdditionalInfo().isNull()) { + processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD); + processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD); + } + return user; } catch (Exception e) { throw handleException(e); } @@ -339,4 +344,5 @@ public class UserController extends BaseController { throw handleException(e); } } + } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index d4a5d42320..d0a3984660 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -54,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; @@ -78,7 +79,7 @@ import java.util.stream.Collectors; @Slf4j @Service -public class DefaultTbApiUsageStateService implements TbApiUsageStateService { +public class DefaultTbApiUsageStateService extends TbApplicationEventListener implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @@ -188,7 +189,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } @Override - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index 9b403fad1a..00a5bca503 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.NodeConfiguration; @@ -71,7 +72,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe private ObjectMapper mapper = new ObjectMapper(); private boolean isInstall() { - return environment.acceptsProfiles("install"); + return environment.acceptsProfiles(Profiles.of("install")); } @PostConstruct @@ -200,7 +201,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); Class configClazz = nodeAnnotation.configClazz(); - NodeConfiguration config = configClazz.newInstance(); + NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance(); NodeConfiguration defaultConfiguration = config.defaultConfiguration(); nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); nodeDefinition.setUiResources(nodeAnnotation.uiResources()); diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 2793503a81..3bd540048f 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java index 713fcdb158..a03aed014c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java @@ -22,8 +22,8 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.RandomStringUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.RpcError; @@ -99,7 +99,6 @@ public class DeviceProcessor extends BaseProcessor { ObjectNode body = mapper.createObjectNode(); body.put("conflictName", deviceName); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), body); - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } futureToSet.set(null); } @@ -115,7 +114,6 @@ public class DeviceProcessor extends BaseProcessor { log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, device.getId(), null); - deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } break; case ENTITY_UPDATED_RPC_MESSAGE: @@ -201,6 +199,7 @@ public class DeviceProcessor extends BaseProcessor { createRelationFromEdge(tenantId, edge.getId(), device.getId()); deviceStateService.onDeviceAdded(device); pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); + deviceService.assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId()); } finally { deviceCreationLock.unlock(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java index 661e09df2d..c858955bfc 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java @@ -21,10 +21,9 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.gson.Gson; import com.google.gson.JsonObject; -import groovy.lang.Tuple; import lombok.extern.slf4j.Slf4j; -import org.javatuples.Pair; -import org.passay.Rule; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; @@ -37,7 +36,6 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -45,7 +43,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -145,9 +142,9 @@ public class TelemetryProcessor extends BaseProcessor { String defaultQueueName = deviceProfile.getDefaultQueueName(); queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN; } - return new Pair<>(queueName, ruleChainId); + return new ImmutablePair<>(queueName, ruleChainId); } else { - return new Pair<>(ServiceQueue.MAIN, null); + return new ImmutablePair<>(ServiceQueue.MAIN, null); } } @@ -157,8 +154,8 @@ public class TelemetryProcessor extends BaseProcessor { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - String queueName = defaultQueueAndRuleChain.getValue0(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue1(); + String queueName = defaultQueueAndRuleChain.getKey(); + RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override @@ -180,8 +177,8 @@ public class TelemetryProcessor extends BaseProcessor { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - String queueName = defaultQueueAndRuleChain.getValue0(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue1(); + String queueName = defaultQueueAndRuleChain.getKey(); + RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override @@ -207,8 +204,8 @@ public class TelemetryProcessor extends BaseProcessor { @Override public void onSuccess(@Nullable List voids) { Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - String queueName = defaultQueueAndRuleChain.getValue0(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue1(); + String queueName = defaultQueueAndRuleChain.getKey(); + RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, gson.toJson(json), ruleChainId, null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 041953b085..9b3e9fd1c4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -198,7 +198,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { generalSettings.setKey("general"); ObjectNode node = objectMapper.createObjectNode(); node.put("baseUrl", "http://localhost:8080"); - node.put("prohibitDifferentUrl", true); + node.put("prohibitDifferentUrl", false); generalSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); @@ -439,6 +439,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { this.deleteSystemWidgetBundle("input_widgets"); this.deleteSystemWidgetBundle("date"); this.deleteSystemWidgetBundle("entity_admin_widgets"); + this.deleteSystemWidgetBundle("navigation_widgets"); this.deleteSystemWidgetBundle("edge_widgets"); installScripts.loadSystemWidgets(); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java index e8b90ae8fe..b333cafce2 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java @@ -15,11 +15,20 @@ */ package org.thingsboard.server.service.install; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.util.HsqlDao; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; + @Service +@Slf4j @HsqlDao @Profile("install") public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService @@ -27,5 +36,21 @@ public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSe protected HsqlEntityDatabaseSchemaService() { super("schema-entities-hsql.sql", "schema-entities-idx.sql"); } + + private final String schemaTypesSql = "schema-types-hsql.sql"; + + @Override + public void createDatabaseSchema(boolean createIndexes) throws Exception { + + log.info("Installing SQL DataBase types part: " + schemaTypesSql); + + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaTypesSql); + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8")); + conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema + } + + super.createDatabaseSchema(createIndexes); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java index f823c40e69..1e652880a0 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlAbstractDatabaseSchemaService.java @@ -30,7 +30,7 @@ import java.sql.SQLException; @Slf4j public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchemaService { - private static final String SQL_DIR = "sql"; + protected static final String SQL_DIR = "sql"; @Value("${spring.datasource.url}") protected String dbUrl; @@ -42,7 +42,7 @@ public abstract class SqlAbstractDatabaseSchemaService implements DatabaseSchema protected String dbPassword; @Autowired - private InstallScripts installScripts; + protected InstallScripts installScripts; private final String schemaSql; private final String schemaIdxSql; diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java index ec648a6800..ca70c4206e 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java +++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java @@ -145,17 +145,17 @@ public class CassandraDbHelper { if (row.isNull(index)) { return null; } else if (type.getProtocolCode() == ProtocolConstants.DataType.DOUBLE) { - str = new Double(row.getDouble(index)).toString(); + str = Double.valueOf(row.getDouble(index)).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.INT) { - str = new Integer(row.getInt(index)).toString(); + str = Integer.valueOf(row.getInt(index)).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.BIGINT) { - str = new Long(row.getLong(index)).toString(); + str = Long.valueOf(row.getLong(index)).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.UUID) { str = row.getUuid(index).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMEUUID) { str = row.getUuid(index).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.FLOAT) { - str = new Float(row.getFloat(index)).toString(); + str = Float.valueOf(row.getFloat(index)).toString(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.TIMESTAMP) { str = ""+row.getInstant(index).toEpochMilli(); } else if (type.getProtocolCode() == ProtocolConstants.DataType.BOOLEAN) { diff --git a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java index bc53134973..f841621bbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java +++ b/application/src/main/java/org/thingsboard/server/service/install/migrate/CassandraToSqlColumn.java @@ -153,7 +153,8 @@ public class CassandraToSqlColumn { sqlInsertStatement.setBoolean(this.sqlIndex, Boolean.parseBoolean(value)); break; case ENUM_TO_INT: - Enum enumVal = Enum.valueOf(this.enumClass, value); + @SuppressWarnings("unchecked") + Enum enumVal = Enum.valueOf(this.enumClass, value); int intValue = enumVal.ordinal(); sqlInsertStatement.setInt(this.sqlIndex, intValue); break; diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 528b5118f1..59a94a10bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -50,7 +50,8 @@ import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; @Service @Profile("install") diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java index 81dd7d7e66..5057893d5c 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java @@ -169,11 +169,11 @@ public class LwM2MModelsRepository { * PageNumber = 1, PageSize = List.size() */ public PageData findLwm2mListObjects(PageLink pageLink) { - PageImpl page = new PageImpl(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()), + PageImpl page = new PageImpl<>(getLwm2mObjects(getObjectIdFromTextSearch(pageLink.getTextSearch()), pageLink.getTextSearch(), pageLink.getSortOrder().getProperty(), pageLink.getSortOrder().getDirection().name())); - PageData pageData = new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); + PageData pageData = new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); return pageData; } @@ -217,23 +217,19 @@ public class LwM2MModelsRepository { switch (mode) { case NO_SEC: bsServ.setHost(contextBootStrap.getBootstrapHost()); - bsServ.setPort(contextBootStrap.getBootstrapPortNoSecPsk()); + bsServ.setPort(contextBootStrap.getBootstrapPortNoSec()); bsServ.setServerPublicKey(""); break; case PSK: - bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePortPsk()); + bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); + bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); bsServ.setServerPublicKey(""); break; case RPK: - bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePortRpk()); - bsServ.setServerPublicKey(getRPKPublicKey(this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); - break; case X509: - bsServ.setHost(contextBootStrap.getBootstrapSecureHost()); - bsServ.setPort(contextBootStrap.getBootstrapSecurePortX509()); - bsServ.setServerPublicKey(getServerPublicKeyX509(contextBootStrap.getBootstrapAlias())); + bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); + bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); + bsServ.setServerPublicKey(getPublicKey (contextBootStrap.getBootstrapAlias(), this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); break; default: break; @@ -243,23 +239,19 @@ public class LwM2MModelsRepository { switch (mode) { case NO_SEC: bsServ.setHost(contextServer.getServerHost()); - bsServ.setPort(contextServer.getServerPortNoSecPsk()); + bsServ.setPort(contextServer.getServerPortNoSec()); bsServ.setServerPublicKey(""); break; case PSK: - bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerPortPsk()); + bsServ.setHost(contextServer.getServerHostSecurity()); + bsServ.setPort(contextServer.getServerPortSecurity()); bsServ.setServerPublicKey(""); break; case RPK: - bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerPortRpk()); - bsServ.setServerPublicKey(getRPKPublicKey(this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY())); - break; case X509: - bsServ.setHost(contextServer.getServerSecureHost()); - bsServ.setPort(contextServer.getServerPortX509()); - bsServ.setServerPublicKey(getServerPublicKeyX509(contextServer.getServerAlias())); + bsServ.setHost(contextServer.getServerHostSecurity()); + bsServ.setPort(contextServer.getServerPortSecurity()); + bsServ.setServerPublicKey(getPublicKey (contextServer.getServerAlias(), this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY())); break; default: break; @@ -268,6 +260,11 @@ public class LwM2MModelsRepository { return bsServ; } + private String getPublicKey (String alias, String publicServerX, String publicServerY) { + String publicKey = getServerPublicKeyX509(alias); + return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY); + } + /** * @param alias * @return PublicKey format HexString or null diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index 39a1c46325..7e1101dfc8 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -206,7 +206,7 @@ public class DefaultEntityQueryService implements EntityQueryService { addItemsToArrayNode(json.putArray("entityTypes"), types); addItemsToArrayNode(json.putArray("timeseries"), timeseriesKeys); addItemsToArrayNode(json.putArray("attribute"), attributesKeys); - response.setResult(new ResponseEntity(json, HttpStatus.OK)); + response.setResult(new ResponseEntity<>(json, HttpStatus.OK)); } private void replyWithEmptyResponse(DeferredResult response) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 0dc200df0f..6ad46bb5b8 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -156,12 +156,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService tpi.newByTopic(usageStatsConsumer.getTopic())) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 98e46d5a50..390798a3e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -140,11 +140,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< } @Override - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { - if (partitionChangeEvent.getServiceType().equals(getServiceType())) { - ServiceQueue serviceQueue = partitionChangeEvent.getServiceQueueKey().getServiceQueue(); - log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), partitionChangeEvent.getPartitions()); - consumers.get(serviceQueue.getQueue()).subscribe(partitionChangeEvent.getPartitions()); + protected void onTbApplicationEvent(PartitionChangeEvent event) { + if (event.getServiceType().equals(getServiceType())) { + ServiceQueue serviceQueue = event.getServiceQueueKey().getServiceQueue(); + log.info("[{}] Subscribing to partitions: {}", serviceQueue.getQueue(), event.getPartitions()); + consumers.get(serviceQueue.getQueue()).subscribe(event.getPartitions()); } } @@ -181,7 +181,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< new TbMsgPackCallback(id, tenantId, ctx, stats.getTimer(tenantId, SUCCESSFUL_STATUS), stats.getTimer(tenantId, FAILED_STATUS)) : new TbMsgPackCallback(id, tenantId, ctx); try { - if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) { + if (!toRuleEngineMsg.getTbMsg().isEmpty()) { forwardToRuleEngineActor(configuration.getName(), tenantId, toRuleEngineMsg, callback); } else { callback.onSuccess(); @@ -209,6 +209,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< if (statsEnabled) { stats.log(result, decision.isCommit()); } + + ctx.cleanup(); + if (decision.isCommit()) { submitStrategy.stop(); break; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java index 4ccdce8ff8..d7a064c4ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java @@ -147,4 +147,10 @@ public class TbMsgPackProcessingContext { .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel())); } } + + public void cleanup() { + pendingMap.clear(); + successMap.clear(); + failedMap.clear(); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 31d5cf47c3..02378eb557 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -36,6 +36,7 @@ import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -56,7 +57,7 @@ import java.util.function.Function; import java.util.stream.Collectors; @Slf4j -public abstract class AbstractConsumerService implements ApplicationListener { +public abstract class AbstractConsumerService extends TbApplicationEventListener { protected volatile ExecutorService consumersExecutor; protected volatile ExecutorService notificationsConsumerExecutor; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java index b75cf4d7ca..64a08b2ef4 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java @@ -31,6 +31,8 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; @RequiredArgsConstructor public class ToDeviceRpcRequestActorMsg implements ToDeviceActorNotificationMsg { + private static final long serialVersionUID = -8592877558138716589L; + @Getter private final String serviceId; @Getter diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 4aae3263d5..1d7a5d9533 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -21,7 +21,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandboxes; -import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -33,6 +32,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.script.Invocable; import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.UUID; import java.util.concurrent.ExecutionException; @@ -97,8 +97,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer sandbox.allowLoadFunctions(true); sandbox.setMaxPreparedStatements(30); } else { - NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); - engine = factory.getScriptEngine(new String[]{"--no-java"}); + ScriptEngineManager factory = new ScriptEngineManager(); + engine = factory.getEngineByName("nashorn"); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java index d9f4b976eb..331bbc6ef6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/SkipPathRequestMatcher.java @@ -29,7 +29,7 @@ public class SkipPathRequestMatcher implements RequestMatcher { private RequestMatcher processingMatcher; public SkipPathRequestMatcher(List pathsToSkip, String processingPath) { - Assert.notNull(pathsToSkip); + Assert.notNull(pathsToSkip, "List of paths to skip is required."); List m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList()); matchers = new OrRequestMatcher(m); processingMatcher = new AntPathRequestMatcher(processingPath); diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index b4e26c1447..cfbf051cad 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -100,6 +100,7 @@ public class JwtTokenFactory { Jws jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); Claims claims = jwsClaims.getBody(); String subject = claims.getSubject(); + @SuppressWarnings("unchecked") List scopes = claims.get(SCOPES, List.class); if (scopes == null || scopes.isEmpty()) { throw new IllegalArgumentException("JWT Token doesn't have any scopes"); @@ -155,6 +156,7 @@ public class JwtTokenFactory { Jws jwsClaims = rawAccessToken.parseClaims(settings.getTokenSigningKey()); Claims claims = jwsClaims.getBody(); String subject = claims.getSubject(); + @SuppressWarnings("unchecked") List scopes = claims.get(SCOPES, List.class); if (scopes == null || scopes.isEmpty()) { throw new IllegalArgumentException("Refresh Token doesn't have any scopes"); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index 702042dabb..8067562279 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -48,6 +48,7 @@ public class CustomerUserPermissions extends AbstractPermissions { Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES) { @Override + @SuppressWarnings("unchecked") public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { if (!super.hasPermission(user, operation, entityId, entity)) { @@ -70,6 +71,7 @@ public class CustomerUserPermissions extends AbstractPermissions { new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { @Override + @SuppressWarnings("unchecked") public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { if (!super.hasPermission(user, operation, entityId, entity)) { return false; @@ -120,6 +122,7 @@ public class CustomerUserPermissions extends AbstractPermissions { private static final PermissionChecker widgetsPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) { @Override + @SuppressWarnings("unchecked") public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { if (!super.hasPermission(user, operation, entityId, entity)) { return false; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java index 624e1743ca..0e56963464 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/DefaultAccessControlService.java @@ -56,6 +56,7 @@ public class DefaultAccessControlService implements AccessControlService { } @Override + @SuppressWarnings("unchecked") public void checkPermission(SecurityUser user, Resource resource, Operation operation, I entityId, T entity) throws ThingsboardException { PermissionChecker permissionChecker = getPermissionChecker(user.getAuthority(), resource); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 2ee5c431b5..086dcaf6a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -60,6 +60,7 @@ public class TenantAdminPermissions extends AbstractPermissions { new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY) { @Override + @SuppressWarnings("unchecked") public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { if (!super.hasPermission(user, operation, entityId, entity)) { return false; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 9ebff31ad4..c86b1cdcfe 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.security.system; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -49,6 +49,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; import org.thingsboard.server.utils.MiscUtils; @@ -65,8 +66,6 @@ import static org.thingsboard.server.common.data.CacheConstants.SECURITY_SETTING @Slf4j public class DefaultSystemSecurityService implements SystemSecurityService { - private static final ObjectMapper objectMapper = new ObjectMapper(); - @Autowired private AdminSettingsService adminSettingsService; @@ -89,7 +88,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(tenantId, "securitySettings"); if (adminSettings != null) { try { - securitySettings = objectMapper.treeToValue(adminSettings.getJsonValue(), SecuritySettings.class); + securitySettings = JacksonUtil.convertValue(adminSettings.getJsonValue(), SecuritySettings.class); } catch (Exception e) { throw new RuntimeException("Failed to load security settings!", e); } @@ -109,10 +108,10 @@ public class DefaultSystemSecurityService implements SystemSecurityService { adminSettings = new AdminSettings(); adminSettings.setKey("securitySettings"); } - adminSettings.setJsonValue(objectMapper.valueToTree(securitySettings)); + adminSettings.setJsonValue(JacksonUtil.valueToTree(securitySettings)); AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings); try { - return objectMapper.treeToValue(savedAdminSettings.getJsonValue(), SecuritySettings.class); + return JacksonUtil.convertValue(savedAdminSettings.getJsonValue(), SecuritySettings.class); } catch (Exception e) { throw new RuntimeException("Failed to load security settings!", e); } @@ -189,7 +188,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { JsonNode additionalInfo = user.getAdditionalInfo(); if (additionalInfo instanceof ObjectNode && additionalInfo.has(UserServiceImpl.USER_PASSWORD_HISTORY)) { JsonNode userPasswordHistoryJson = additionalInfo.get(UserServiceImpl.USER_PASSWORD_HISTORY); - Map userPasswordHistoryMap = objectMapper.convertValue(userPasswordHistoryJson, Map.class); + Map userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {}); for (Map.Entry entry : userPasswordHistoryMap.entrySet()) { if (encoder.matches(password, entry.getValue()) && Long.parseLong(entry.getKey()) > passwordReuseFrequencyTs) { throw new DataValidationException("Password was already used for the last " + passwordPolicy.getPasswordReuseFrequencyDays() + " days"); diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 75577fc5a4..be31a0df45 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -56,9 +56,11 @@ import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; +import org.thingsboard.server.utils.EventDeduplicationExecutor; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -89,7 +91,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @Service @TbCoreComponent @Slf4j -public class DefaultDeviceStateService implements DeviceStateService { +public class DefaultDeviceStateService extends TbApplicationEventListener implements DeviceStateService { public static final String ACTIVITY_STATE = "active"; public static final String LAST_CONNECT_TIME = "lastConnectTime"; @@ -126,13 +128,13 @@ public class DefaultDeviceStateService implements DeviceStateService { @Getter private int initFetchPackSize; - private volatile boolean clusterUpdatePending = false; - private ListeningScheduledExecutorService queueExecutor; private final ConcurrentMap> partitionedDevices = new ConcurrentHashMap<>(); private final ConcurrentMap deviceStates = new ConcurrentHashMap<>(); private final ConcurrentMap deviceLastReportedActivity = new ConcurrentHashMap<>(); private final ConcurrentMap deviceLastSavedActivity = new ConcurrentHashMap<>(); + private volatile EventDeduplicationExecutor> deduplicationExecutor; + public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, AttributesService attributesService, TimeseriesService tsService, @@ -155,6 +157,7 @@ public class DefaultDeviceStateService implements DeviceStateService { // Should be always single threaded due to absence of locks. queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state"))); queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); + deduplicationExecutor = new EventDeduplicationExecutor<>(DefaultDeviceStateService.class.getSimpleName(), queueExecutor, this::initStateFromDB); } @PreDestroy @@ -204,7 +207,6 @@ public class DefaultDeviceStateService implements DeviceStateService { if (!state.isActive()) { state.setActive(true); save(deviceId, ACTIVITY_STATE, state.isActive()); - stateData.getMetaData().putValue("scope", SERVER_SCOPE); pushRuleEngineMessage(stateData, ACTIVITY_EVENT); } } @@ -292,25 +294,14 @@ public class DefaultDeviceStateService implements DeviceStateService { } } - volatile Set pendingPartitions; - @Override - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { - synchronized (this) { - pendingPartitions = partitionChangeEvent.getPartitions(); - if (!clusterUpdatePending) { - clusterUpdatePending = true; - queueExecutor.submit(() -> { - clusterUpdatePending = false; - initStateFromDB(); - }); - } - } + deduplicationExecutor.submit(partitionChangeEvent.getPartitions()); } } - private void initStateFromDB() { + private void initStateFromDB(Set pendingPartitions) { try { log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet()); log.info("NEW PARTITIONS: {}", pendingPartitions); @@ -456,7 +447,7 @@ public class DefaultDeviceStateService implements DeviceStateService { } private Function, DeviceStateData> extractDeviceStateData(Device device) { - return new Function, DeviceStateData>() { + return new Function<>() { @Nullable @Override public DeviceStateData apply(@Nullable List data) { @@ -512,7 +503,11 @@ public class DefaultDeviceStateService implements DeviceStateService { } else { data = JacksonUtil.toString(state); } - TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON, data); + TbMsgMetaData md = stateData.getMetaData().copy(); + if(!persistToTelemetry){ + md.putValue(DataConstants.SCOPE, SERVER_SCOPE); + } + TbMsg tbMsg = TbMsg.newMsg(msgType, stateData.getDeviceId(), md, TbMsgDataType.JSON, data); clusterService.pushMsgToRuleEngine(stateData.getTenantId(), stateData.getDeviceId(), tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index bafbb45ba8..844db92de8 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -48,6 +48,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -76,7 +77,7 @@ import java.util.function.Predicate; @Slf4j @TbCoreComponent @Service -public class DefaultSubscriptionManagerService implements SubscriptionManagerService { +public class DefaultSubscriptionManagerService extends TbApplicationEventListener implements SubscriptionManagerService { @Autowired private AttributesService attrService; @@ -178,7 +179,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer } @Override - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { Set removedPartitions = new HashSet<>(currentPartitions); removedPartitions.removeAll(partitionChangeEvent.getPartitions()); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 124823a6ad..689f80f757 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -302,7 +302,9 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); - ctx.setAndResolveQuery(cmd.getQuery()); + if (cmd.getQuery() != null) { + ctx.setAndResolveQuery(cmd.getQuery()); + } sessionSubs.put(cmd.getCmdId(), ctx); return ctx; } @@ -316,6 +318,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc return ctx; } + @SuppressWarnings("unchecked") private T getSubCtx(String sessionId, int cmdId) { Map sessionSubs = subscriptionsBySessionId.get(sessionId); if (sessionSubs != null) { @@ -473,7 +476,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc public void cancelAllSessionSubscriptions(String sessionId) { Map sessionSubs = subscriptionsBySessionId.remove(sessionId); if (sessionSubs != null) { - sessionSubs.values().stream().filter(sub -> sub instanceof TbEntityDataSubCtx).map(sub -> (TbEntityDataSubCtx) sub).forEach(this::cleanupAndCancel); + sessionSubs.values().forEach(this::cleanupAndCancel); } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 868d14ac3e..0220a94964 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; @@ -62,6 +63,34 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer private SubscriptionManagerService subscriptionManagerService; private ExecutorService subscriptionUpdateExecutor; + + private TbApplicationEventListener partitionChangeListener = new TbApplicationEventListener<>() { + @Override + protected void onTbApplicationEvent(PartitionChangeEvent event) { + if (ServiceType.TB_CORE.equals(event.getServiceType())) { + currentPartitions.clear(); + currentPartitions.addAll(event.getPartitions()); + } + } + }; + + private TbApplicationEventListener clusterTopologyChangeListener = new TbApplicationEventListener<>() { + @Override + protected void onTbApplicationEvent(ClusterTopologyChangeEvent event) { + if (event.getServiceQueueKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) { + /* + * If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again. + * Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart. + * Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically + * It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService. + * Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache + * Since number of subscriptions is usually much less then number of devices that are pushing data. + */ + subscriptionsBySessionId.values().forEach(map -> map.values() + .forEach(sub -> pushSubscriptionToManagerService(sub, true))); + } + } + }; @PostConstruct public void initExecutor() { @@ -77,28 +106,14 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Override @EventListener(PartitionChangeEvent.class) - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { - if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { - currentPartitions.clear(); - currentPartitions.addAll(partitionChangeEvent.getPartitions()); - } + public void onApplicationEvent(PartitionChangeEvent event) { + partitionChangeListener.onApplicationEvent(event); } @Override @EventListener(ClusterTopologyChangeEvent.class) public void onApplicationEvent(ClusterTopologyChangeEvent event) { - if (event.getServiceQueueKeys().stream().anyMatch(key -> ServiceType.TB_CORE.equals(key.getServiceType()))) { - /* - * If the cluster topology has changed, we need to push all current subscriptions to SubscriptionManagerService again. - * Otherwise, the SubscriptionManagerService may "forget" those subscriptions in case of restart. - * Although this is resource consuming operation, it is cheaper than sending ping/pong commands periodically - * It is also cheaper then caching the subscriptions by entity id and then lookup of those caches every time we have new telemetry in SubscriptionManagerService. - * Even if we cache locally the list of active subscriptions by entity id, it is still time consuming operation to get them from cache - * Since number of subscriptions is usually much less then number of devices that are pushing data. - */ - subscriptionsBySessionId.values().forEach(map -> map.values() - .forEach(sub -> pushSubscriptionToManagerService(sub, true))); - } + clusterTopologyChangeListener.onApplicationEvent(event); } //TODO 3.1: replace null callbacks with callbacks from websocket service. @@ -123,6 +138,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer } @Override + @SuppressWarnings("unchecked") public void onSubscriptionUpdate(String sessionId, TelemetrySubscriptionUpdate update, TbCallback callback) { TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); @@ -143,6 +159,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer } @Override + @SuppressWarnings("unchecked") public void onSubscriptionUpdate(String sessionId, AlarmSubscriptionUpdate update, TbCallback callback) { TbSubscription subscription = subscriptionsBySessionId .getOrDefault(sessionId, Collections.emptyMap()).get(update.getSubscriptionId()); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 8b0f5b3a94..a200f31a3f 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -107,7 +107,7 @@ public abstract class TbAbstractDataSubCtx { +public abstract class AbstractSubscriptionService extends TbApplicationEventListener{ protected final Set currentPartitions = ConcurrentHashMap.newKeySet(); @@ -97,8 +98,7 @@ public abstract class AbstractSubscriptionService implements ApplicationListener } @Override - @EventListener(PartitionChangeEvent.class) - public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { currentPartitions.clear(); currentPartitions.addAll(partitionChangeEvent.getPartitions()); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java index d542bdbe61..74d81c0307 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java @@ -34,6 +34,8 @@ import java.util.UUID; @Data public class TransportToDeviceActorMsgWrapper implements TbActorMsg, DeviceAwareMsg, TenantAwareMsg, Serializable { + private static final long serialVersionUID = 7191333353202935941L; + private final TenantId tenantId; private final DeviceId deviceId; private final TransportToDeviceActorMsg msg; diff --git a/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java b/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java new file mode 100644 index 0000000000..4ce0d2f3d5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.utils; + +import lombok.extern.slf4j.Slf4j; + +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; + +/** + * This class deduplicate executions of the specified function. + * Useful in cluster mode, when you get event about partition change multiple times. + * Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and + * later, once the processing of first event is done, process last pending task. + * + * @param

parameters of the function + */ +@Slf4j +public class EventDeduplicationExecutor

{ + private final String name; + private final ExecutorService executor; + private final Consumer

function; + private P pendingTask; + private boolean busy; + + public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer

function) { + this.name = name; + this.executor = executor; + this.function = function; + } + + public void submit(P params) { + log.info("[{}] Going to submit: {}", name, params); + synchronized (EventDeduplicationExecutor.this) { + if (!busy) { + busy = true; + pendingTask = null; + try { + log.info("[{}] Submitting task: {}", name, params); + executor.submit(() -> { + try { + log.info("[{}] Executing task: {}", name, params); + function.accept(params); + } catch (Throwable e) { + log.warn("[{}] Failed to process task with parameters: {}", name, params, e); + throw e; + } finally { + unlockAndProcessIfAny(); + } + }); + } catch (Throwable e) { + log.warn("[{}] Failed to submit task with parameters: {}", name, params, e); + unlockAndProcessIfAny(); + throw e; + } + } else { + log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params); + pendingTask = params; + } + } + } + + private void unlockAndProcessIfAny() { + synchronized (EventDeduplicationExecutor.this) { + busy = false; + if (pendingTask != null) { + submit(pendingTask); + } + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java index 6ca82aa062..8ee5a5ebc7 100644 --- a/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/MiscUtils.java @@ -33,6 +33,7 @@ public class MiscUtils { return "The " + propertyName + " property need to be set!"; } + @SuppressWarnings("deprecation") public static HashFunction forName(String name) { switch (name) { case "murmur3_32": diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index d5124d5847..0d1f431a59 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -34,6 +34,9 @@ + + + @@ -43,4 +46,4 @@ - \ No newline at end of file + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 982ed87e94..4c516e48f2 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -118,6 +118,15 @@ security: githubMapper: emailUrl: "${SECURITY_OAUTH2_GITHUB_MAPPER_EMAIL_URL_KEY:https://api.github.com/user/emails}" +# Usage statistics parameters +usage: + stats: + report: + enabled: "${USAGE_STATS_REPORT_ENABLED:true}" + interval: "${USAGE_STATS_REPORT_INTERVAL:10}" + check: + cycle: "${USAGE_STATS_CHECK_CYCLE:60000}" + # Dashboard parameters dashboard: # Maximum allowed datapoints fetched by widgets @@ -585,14 +594,14 @@ transport: # model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" - recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}" request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}" registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" secure: - # Only Certificate_x509: + # Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" @@ -601,48 +610,38 @@ transport: key_store_path_file: "${KEY_STORE_PATH_FILE:}" key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}" root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" - enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" + enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}" server: id: "${LWM2M_SERVER_ID:123}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}" - bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}" - bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}" + bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}" secure: - bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - start_psk: "${START_SERVER_PSK:true}" - start_rpk: "${START_SERVER_RPK:true}" - start_x509: "${START_SERVER_X509:true}" - bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}" - bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}" - bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}" - # Only RPK: Public & Private Key -# create_rpk: "${CREATE_RPK:}" - public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" - public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}" - private_s: "${LWM2M_SERVER_PRIVATE_S:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}" - # Only Certificate_x509: + bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" + bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}" + # create_rpk: "${CREATE_RPK:}" + # Only for RPK: Public & Private Key. If the keystore file is missing or not working + # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d0301070342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b] + # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b], + # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] + public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" + public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" + private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" bootstrap: - enable: "${BOOTSTRAP:true}" + enable: "${LWM2M_BOOTSTRAP_ENABLED:true}" id: "${LWM2M_SERVER_ID:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}" - bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}" - bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}" + bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}" secure: - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - start_psk: "${START_SERVER_PSK_BS:true}" - start_rpk: "${START_SERVER_RPK_BS:true}" - start_x509: "${START_SERVER_X509_BS:true}" - bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}" - bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}" - bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}" - # Only RPK: Public & Private Key - public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" - public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" - private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" - # Only Certificate_x509: + bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" + bind_port_security: "${LWM2M_BIND_PORT_SEC_BS:5688}" + # Only for RPK: Public & Private Key. If the keystore file is missing or not working + # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] + # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34] + # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34], + public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" + public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" + private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}" # Redis redis_url: "${LWM2M_REDIS_URL:''}" @@ -713,6 +712,10 @@ queue: transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + consumer-stats: + enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" + print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" + kafka-response-timeout-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}" aws_sqs: use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index c133c17a2a..c615a88142 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -377,6 +377,10 @@ public abstract class AbstractWebTest { return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseClass); } + protected T doGetAsyncTyped(String urlTemplate, TypeReference responseType, Object... urlVariables) throws Exception { + return readResponse(doGetAsync(urlTemplate, urlVariables).andExpect(status().isOk()), responseType); + } + protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { MockHttpServletRequestBuilder getRequest; getRequest = get(urlTemplate, urlVariables); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index 5f1aab4adc..e651caa2ff 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -352,8 +352,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Thread.sleep(1000); - List> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + List> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); assertEquals("value1", getValue(values, "caKey1")); assertEquals(true, getValue(values, "caKey2")); @@ -369,8 +369,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Set expectedActualAttributesSet = new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4")); assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); - List> valueTelemetryOfDevices = doGetAsync("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + List> valueTelemetryOfDevices = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); EntityView view = new EntityView(); view.setEntityId(testDevice.getId()); @@ -384,8 +384,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Thread.sleep(1000); - List> values = doGetAsync("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), List.class); + List> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); assertEquals(0, values.size()); } @@ -454,12 +454,12 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes } private Set getTelemetryKeys(String type, String id) throws Exception { - return new HashSet<>(doGetAsync("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", List.class)); + return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", new TypeReference<>() {})); } private Map>> getTelemetryValues(String type, String id, Set keys, Long startTs, Long endTs) throws Exception { - return doGetAsync("/api/plugins/telemetry/" + type + "/" + id + - "/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, Map.class); + return doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + + "/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, new TypeReference<>() {}); } private Set getAttributesByKeys(String stringKV) throws Exception { @@ -484,7 +484,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes client.publish("v1/devices/me/attributes", message); Thread.sleep(1000); client.disconnect(); - return new HashSet<>(doGetAsync("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", List.class)); + return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", new TypeReference<>() {})); } private Object getValue(List> values, String stringValue) { diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 41cfcf03ab..e8e71f557c 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -26,7 +26,7 @@ import com.google.protobuf.AbstractMessage; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -1216,12 +1216,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { // Wait before device attributes saved to database before requesting them from controller Thread.sleep(1000); - Map>> timeseries = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, Map.class); + Map>> timeseries = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, new TypeReference<>() {}); Assert.assertTrue(timeseries.containsKey(timeseriesKey)); Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); - List> attributes = doGetAsync("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, List.class); + List> attributes = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, new TypeReference<>() {}); Assert.assertEquals(1, attributes.size()); Assert.assertEquals(attributes.get(0).get("key"), attributesKey); Assert.assertEquals(attributes.get(0).get("value"), attributesValue); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java index d048546323..874d55606d 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.mqtt.telemetry.attributes; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.After; @@ -80,7 +81,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt List actualKeys = null; while (start <= end) { - actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", List.class); + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); if (actualKeys.size() == expectedKeys.size()) { break; } @@ -96,7 +97,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt assertEquals(expectedKeySet, actualKeySet); String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet); - List> values = doGetAsync(getAttributesValuesUrl, List.class); + List> values = doGetAsyncTyped(getAttributesValuesUrl, new TypeReference<>() {}); assertAttributesValues(values, expectedKeySet); String deleteAttributesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet); doDelete(deleteAttributesUrl); @@ -121,10 +122,10 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt Thread.sleep(2000); - List firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class); + List firstDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); Set firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys); - List secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/attributes/CLIENT_SCOPE", List.class); + List secondDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); Set secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys); Set expectedKeySet = new HashSet<>(expectedKeys); @@ -135,14 +136,15 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt String getAttributesValuesUrlFirstDevice = getAttributesValuesUrl(firstDevice.getId(), firstDeviceActualKeySet); String getAttributesValuesUrlSecondDevice = getAttributesValuesUrl(firstDevice.getId(), secondDeviceActualKeySet); - List> firstDeviceValues = doGetAsync(getAttributesValuesUrlFirstDevice, List.class); - List> secondDeviceValues = doGetAsync(getAttributesValuesUrlSecondDevice, List.class); + List> firstDeviceValues = doGetAsyncTyped(getAttributesValuesUrlFirstDevice, new TypeReference<>() {}); + List> secondDeviceValues = doGetAsyncTyped(getAttributesValuesUrlSecondDevice, new TypeReference<>() {}); assertAttributesValues(firstDeviceValues, expectedKeySet); assertAttributesValues(secondDeviceValues, expectedKeySet); } + @SuppressWarnings("unchecked") protected void assertAttributesValues(List> deviceValues, Set expectedKeySet) throws JsonProcessingException { for (Map map : deviceValues) { String key = (String) map.get("key"); diff --git a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index 9984c9faae..9ecc3415c2 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.mqtt.telemetry.timeseries; +import com.fasterxml.jackson.core.type.TypeReference; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; @@ -25,6 +26,7 @@ import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; @@ -107,7 +109,7 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt List actualKeys = null; while (start <= end) { - actualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", List.class); + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {}); if (actualKeys.size() == expectedKeys.size()) { break; } @@ -129,13 +131,13 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } start = System.currentTimeMillis(); end = System.currentTimeMillis() + 5000; - Map>> values = null; + Map>> values = null; while (start <= end) { - values = doGetAsync(getTelemetryValuesUrl, Map.class); + values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {}); boolean valid = values.size() == expectedKeys.size(); if (valid) { for (String key : expectedKeys) { - List> tsValues = values.get(key); + List> tsValues = values.get(key); if (tsValues != null && tsValues.size() > 0) { Object ts = tsValues.get(0).get("ts"); if (ts == null) { @@ -181,10 +183,10 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt Thread.sleep(2000); - List firstDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/timeseries", List.class); + List firstDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + firstDevice.getId() + "/keys/timeseries", new TypeReference<>() {}); Set firstDeviceActualKeySet = new HashSet<>(firstDeviceActualKeys); - List secondDeviceActualKeys = doGetAsync("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/timeseries", List.class); + List secondDeviceActualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + secondDevice.getId() + "/keys/timeseries", new TypeReference<>() {}); Set secondDeviceActualKeySet = new HashSet<>(secondDeviceActualKeys); Set expectedKeySet = new HashSet<>(expectedKeys); @@ -195,8 +197,8 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt String getTelemetryValuesUrlFirstDevice = getTelemetryValuesUrl(firstDevice.getId(), firstDeviceActualKeySet); String getTelemetryValuesUrlSecondDevice = getTelemetryValuesUrl(firstDevice.getId(), secondDeviceActualKeySet); - Map>> firstDeviceValues = doGetAsync(getTelemetryValuesUrlFirstDevice, Map.class); - Map>> secondDeviceValues = doGetAsync(getTelemetryValuesUrlSecondDevice, Map.class); + Map>> firstDeviceValues = doGetAsyncTyped(getTelemetryValuesUrlFirstDevice, new TypeReference<>() {}); + Map>> secondDeviceValues = doGetAsyncTyped(getTelemetryValuesUrlSecondDevice, new TypeReference<>() {}); assertGatewayDeviceData(firstDeviceValues, expectedKeys); assertGatewayDeviceData(secondDeviceValues, expectedKeys); @@ -212,7 +214,7 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?startTs=0&endTs=25000&keys=" + String.join(",", actualKeySet); } - private void assertGatewayDeviceData(Map>> deviceValues, List expectedKeys) { + private void assertGatewayDeviceData(Map>> deviceValues, List expectedKeys) { assertEquals(2, deviceValues.get(expectedKeys.get(0)).size()); assertEquals(2, deviceValues.get(expectedKeys.get(1)).size()); @@ -228,11 +230,11 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } - private void assertValues(Map>> deviceValues, int arrayIndex) { - for (Map.Entry>> entry : deviceValues.entrySet()) { + private void assertValues(Map>> deviceValues, int arrayIndex) { + for (Map.Entry>> entry : deviceValues.entrySet()) { String key = entry.getKey(); - List> tsKv = entry.getValue(); - String value = tsKv.get(arrayIndex).get("value"); + List> tsKv = entry.getValue(); + String value = (String) tsKv.get(arrayIndex).get("value"); switch (key) { case "key1": assertEquals("value1", value); @@ -253,7 +255,7 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } } - private void assertTs(Map>> deviceValues, List expectedKeys, int ts, int arrayIndex) { + private void assertTs(Map>> deviceValues, List expectedKeys, int ts, int arrayIndex) { assertEquals(ts, deviceValues.get(expectedKeys.get(0)).get(arrayIndex).get("ts")); assertEquals(ts, deviceValues.get(expectedKeys.get(1)).get(arrayIndex).get("ts")); assertEquals(ts, deviceValues.get(expectedKeys.get(2)).get(arrayIndex).get("ts")); diff --git a/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java b/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java index 7ce23368a9..88d7796af5 100644 --- a/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java @@ -21,12 +21,11 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.context.ApplicationEventPublisher; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.queue.discovery.HashPartitionService; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; diff --git a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java index 901fc27bb1..6c4e4a9426 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java @@ -20,7 +20,7 @@ import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; diff --git a/application/src/test/java/org/thingsboard/server/util/EventDeduplicationExecutorTest.java b/application/src/test/java/org/thingsboard/server/util/EventDeduplicationExecutorTest.java new file mode 100644 index 0000000000..ac0b2342ec --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/util/EventDeduplicationExecutorTest.java @@ -0,0 +1,155 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.util; + +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.server.utils.EventDeduplicationExecutor; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class EventDeduplicationExecutorTest { + + @Test + public void testSimpleFlowSameThread() throws InterruptedException { + simpleFlow(MoreExecutors.newDirectExecutorService()); + } + + @Test + public void testPeriodicFlowSameThread() throws InterruptedException { + periodicFlow(MoreExecutors.newDirectExecutorService()); + } + + @Test + public void testExceptionFlowSameThread() throws InterruptedException { + exceptionFlow(MoreExecutors.newDirectExecutorService()); + } + + @Test + public void testSimpleFlowSingleThread() throws InterruptedException { + simpleFlow(Executors.newSingleThreadExecutor()); + } + + @Test + public void testPeriodicFlowSingleThread() throws InterruptedException { + periodicFlow(Executors.newSingleThreadExecutor()); + } + + @Test + public void testExceptionFlowSingleThread() throws InterruptedException { + exceptionFlow(Executors.newSingleThreadExecutor()); + } + + @Test + public void testSimpleFlowMultiThread() throws InterruptedException { + simpleFlow(Executors.newFixedThreadPool(3)); + } + + @Test + public void testPeriodicFlowMultiThread() throws InterruptedException { + periodicFlow(Executors.newFixedThreadPool(3)); + } + + @Test + public void testExceptionFlowMultiThread() throws InterruptedException { + exceptionFlow(Executors.newFixedThreadPool(3)); + } + + private void simpleFlow(ExecutorService executorService) throws InterruptedException { + try { + Consumer function = Mockito.spy(StringConsumer.class); + EventDeduplicationExecutor executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); + + String params1 = "params1"; + String params2 = "params2"; + String params3 = "params3"; + + executor.submit(params1); + executor.submit(params2); + executor.submit(params3); + Thread.sleep(500); + Mockito.verify(function).accept(params1); + Mockito.verify(function).accept(params3); + } finally { + executorService.shutdownNow(); + } + } + + private void periodicFlow(ExecutorService executorService) throws InterruptedException { + try { + Consumer function = Mockito.spy(StringConsumer.class); + EventDeduplicationExecutor executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); + + String params1 = "params1"; + String params2 = "params2"; + String params3 = "params3"; + + executor.submit(params1); + Thread.sleep(500); + executor.submit(params2); + Thread.sleep(500); + executor.submit(params3); + Thread.sleep(500); + Mockito.verify(function).accept(params1); + Mockito.verify(function).accept(params2); + Mockito.verify(function).accept(params3); + } finally { + executorService.shutdownNow(); + } + } + + private void exceptionFlow(ExecutorService executorService) throws InterruptedException { + try { + Consumer function = Mockito.spy(StringConsumer.class); + EventDeduplicationExecutor executor = new EventDeduplicationExecutor<>(EventDeduplicationExecutorTest.class.getSimpleName(), executorService, function); + + String params1 = "params1"; + String params2 = "params2"; + String params3 = "params3"; + + Mockito.doThrow(new RuntimeException()).when(function).accept("params1"); + executor.submit(params1); + executor.submit(params2); + Thread.sleep(500); + executor.submit(params3); + Thread.sleep(500); + Mockito.verify(function).accept(params2); + Mockito.verify(function).accept(params3); + } finally { + executorService.shutdownNow(); + } + } + + public static class StringConsumer implements Consumer { + @Override + public void accept(String s) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } + +} diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 6609648e7c..1f4849cb79 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -67,7 +67,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java b/common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java index 4acadaff2c..0d2ec046a0 100644 --- a/common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java +++ b/common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java @@ -30,7 +30,7 @@ public interface TbActor { } default InitFailureStrategy onInitFailure(int attempt, Throwable t) { - return InitFailureStrategy.retryWithDelay(5000 * attempt); + return InitFailureStrategy.retryWithDelay(5000L * attempt); } default ProcessFailureStrategy onProcessFailure(Throwable t) { diff --git a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java index 66ccb3f81c..86329cc641 100644 --- a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java +++ b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java @@ -17,6 +17,8 @@ package org.thingsboard.server.actors; public class TbActorException extends Exception { + private static final long serialVersionUID = 8209771144711980882L; + public TbActorException(String message, Throwable cause) { super(message, cause); } diff --git a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java index 225b9351ad..25dd8694f8 100644 --- a/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java +++ b/common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java @@ -17,7 +17,9 @@ package org.thingsboard.server.actors; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbActorStopReason; import java.util.List; import java.util.concurrent.ConcurrentLinkedQueue; @@ -49,6 +51,7 @@ public final class TbActorMailbox implements TbActorCtx { private final AtomicBoolean busy = new AtomicBoolean(FREE); private final AtomicBoolean ready = new AtomicBoolean(NOT_READY); private final AtomicBoolean destroyInProgress = new AtomicBoolean(); + private volatile TbActorStopReason stopReason; public void initActor() { dispatcher.getExecutor().execute(() -> tryInit(1)); @@ -70,7 +73,8 @@ public final class TbActorMailbox implements TbActorCtx { InitFailureStrategy strategy = actor.onInitFailure(attempt, t); if (strategy.isStop() || (settings.getMaxActorInitAttempts() > 0 && attemptIdx > settings.getMaxActorInitAttempts())) { log.info("[{}] Failed to init actor, attempt {}, going to stop attempts.", selfId, attempt, t); - system.stop(selfId); + stopReason = TbActorStopReason.INIT_FAILED; + destroy(); } else if (strategy.getRetryDelay() > 0) { log.info("[{}] Failed to init actor, attempt {}, going to retry in attempts in {}ms", selfId, attempt, strategy.getRetryDelay()); log.debug("[{}] Error", selfId, t); @@ -84,12 +88,28 @@ public final class TbActorMailbox implements TbActorCtx { } private void enqueue(TbActorMsg msg, boolean highPriority) { - if (highPriority) { - highPriorityMsgs.add(msg); + if (!destroyInProgress.get()) { + if (highPriority) { + highPriorityMsgs.add(msg); + } else { + normalPriorityMsgs.add(msg); + } + tryProcessQueue(true); } else { - normalPriorityMsgs.add(msg); + if (highPriority && msg.getMsgType().equals(MsgType.RULE_NODE_UPDATED_MSG)) { + synchronized (this) { + if (stopReason == TbActorStopReason.INIT_FAILED) { + destroyInProgress.set(false); + stopReason = null; + initActor(); + } else { + msg.onTbActorStopped(stopReason); + } + } + } else { + msg.onTbActorStopped(stopReason); + } } - tryProcessQueue(true); } private void tryProcessQueue(boolean newMsg) { @@ -119,6 +139,9 @@ public final class TbActorMailbox implements TbActorCtx { try { log.debug("[{}] Going to process message: {}", selfId, msg); actor.process(msg); + } catch (TbRuleNodeUpdateException updateException){ + stopReason = TbActorStopReason.INIT_FAILED; + destroy(); } catch (Throwable t) { log.debug("[{}] Failed to process message: {}", selfId, msg, t); ProcessFailureStrategy strategy = actor.onProcessFailure(t); @@ -180,11 +203,16 @@ public final class TbActorMailbox implements TbActorCtx { } public void destroy() { + if (stopReason == null) { + stopReason = TbActorStopReason.STOPPED; + } destroyInProgress.set(true); dispatcher.getExecutor().execute(() -> { try { ready.set(NOT_READY); actor.destroy(); + highPriorityMsgs.forEach(msg -> msg.onTbActorStopped(stopReason)); + normalPriorityMsgs.forEach(msg -> msg.onTbActorStopped(stopReason)); } catch (Throwable t) { log.warn("[{}] Failed to destroy actor: {}", selfId, t); } diff --git a/common/actor/src/main/java/org/thingsboard/server/actors/TbRuleNodeUpdateException.java b/common/actor/src/main/java/org/thingsboard/server/actors/TbRuleNodeUpdateException.java new file mode 100644 index 0000000000..7e3cca863d --- /dev/null +++ b/common/actor/src/main/java/org/thingsboard/server/actors/TbRuleNodeUpdateException.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors; + +public class TbRuleNodeUpdateException extends RuntimeException { + + private static final long serialVersionUID = 8209771144711980882L; + + public TbRuleNodeUpdateException(String message, Throwable cause) { + super(message, cause); + } +} + diff --git a/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java b/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java index 8ce0942f2a..31fae3d7a4 100644 --- a/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java +++ b/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java @@ -21,7 +21,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.common.data.id.DeviceId; import java.util.ArrayList; diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index b427846d91..ba30e5bb43 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -48,6 +48,10 @@ com.google.guava guava + + javax.annotation + javax.annotation-api + com.github.fge json-schema-validator @@ -99,7 +103,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java index d03c0d167b..57b0e2a140 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.thingsboard.server.dao.cassandra.guava.GuavaSession; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionBuilder; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionUtils; @@ -77,7 +78,7 @@ public abstract class AbstractCassandraCluster { } private boolean isInstall() { - return environment.acceptsProfiles("install"); + return environment.acceptsProfiles(Profiles.of("install")); } private void initSession() { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java index 45f3e6fd6a..8187e5e6f2 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java @@ -18,38 +18,25 @@ package org.thingsboard.server.dao.cassandra.guava; import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.config.DriverConfigLoader; import com.datastax.oss.driver.api.core.context.DriverContext; -import com.datastax.oss.driver.api.core.metadata.Node; -import com.datastax.oss.driver.api.core.metadata.NodeStateListener; -import com.datastax.oss.driver.api.core.metadata.schema.SchemaChangeListener; +import com.datastax.oss.driver.api.core.session.ProgrammaticArguments; import com.datastax.oss.driver.api.core.session.SessionBuilder; -import com.datastax.oss.driver.api.core.tracker.RequestTracker; -import com.datastax.oss.driver.api.core.type.codec.TypeCodec; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; public class GuavaSessionBuilder extends SessionBuilder { @Override protected DriverContext buildContext( DriverConfigLoader configLoader, - List> typeCodecs, - NodeStateListener nodeStateListener, - SchemaChangeListener schemaChangeListener, - RequestTracker requestTracker, - Map localDatacenters, - Map> nodeFilters, - ClassLoader classLoader) { + ProgrammaticArguments programmaticArguments) { return new GuavaDriverContext( configLoader, - typeCodecs, - nodeStateListener, - schemaChangeListener, - requestTracker, - localDatacenters, - nodeFilters, - classLoader); + programmaticArguments.getTypeCodecs(), + programmaticArguments.getNodeStateListener(), + programmaticArguments.getSchemaChangeListener(), + programmaticArguments.getRequestTracker(), + programmaticArguments.getLocalDatacenters(), + programmaticArguments.getNodeFilters(), + programmaticArguments.getClassLoader()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java similarity index 81% rename from dao/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java index 1a56ff54e1..43c7318470 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/mapping/JacksonUtil.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.util.mapping; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -38,6 +39,15 @@ public class JacksonUtil { } } + public static T convertValue(Object fromValue, TypeReference toValueTypeRef) { + try { + return fromValue != null ? OBJECT_MAPPER.convertValue(fromValue, toValueTypeRef) : null; + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("The given object value: " + + fromValue + " cannot be converted to " + toValueTypeRef, e); + } + } + public static T fromString(String string, Class clazz) { try { return string != null ? OBJECT_MAPPER.readValue(string, clazz) : null; @@ -72,7 +82,9 @@ public class JacksonUtil { } public static T clone(T value) { - return fromString(toString(value), (Class) value.getClass()); + @SuppressWarnings("unchecked") + Class valueClass = (Class) value.getClass(); + return fromString(toString(value), valueClass); } public static JsonNode valueToTree(T value) { diff --git a/common/data/pom.xml b/common/data/pom.xml index 6d484bd107..79b3bc8e26 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -63,7 +63,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index fb7584e280..d0a27fdbc6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -24,6 +24,7 @@ public class DataConstants { public static final String CUSTOMER = "CUSTOMER"; public static final String DEVICE = "DEVICE"; + public static final String SCOPE = "scope"; public static final String CLIENT_SCOPE = "CLIENT_SCOPE"; public static final String SERVER_SCOPE = "SERVER_SCOPE"; public static final String SHARED_SCOPE = "SHARED_SCOPE"; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java rename to common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java index b62c513780..f2985e3371 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java @@ -13,25 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.actors.ruleChain; +package org.thingsboard.server.common.data; import lombok.Data; -import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.TbMsg; -/** - * Created by ashvayka on 19.03.18. - */ @Data -final class RuleNodeToSelfErrorMsg implements TbActorMsg { +public class HomeDashboard extends Dashboard { - private final TbMsg msg; - private final Throwable error; + private boolean hideDashboardToolbar; - @Override - public MsgType getMsgType() { - return MsgType.RULE_TO_SELF_ERROR_MSG; + public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) { + super(dashboard); + this.hideDashboardToolbar = hideDashboardToolbar; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java new file mode 100644 index 0000000000..1cbc1c737e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.id.DashboardId; + +@Data +@AllArgsConstructor +public class HomeDashboardInfo { + private DashboardId dashboardId; + private boolean hideDashboardToolbar; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java index 8fde14b6ca..8777e08066 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/lwm2m/ServerSecurityConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.data.lwm2m; -import lombok.Builder; import lombok.Data; @Data @@ -23,12 +22,8 @@ public class ServerSecurityConfig { String host; Integer port; String serverPublicKey; - @Builder.Default boolean bootstrapServerIs = true; - @Builder.Default Integer clientHoldOffTime = 1; - @Builder.Default Integer serverId = 111; - @Builder.Default Integer bootstrapServerAccountTimeout = 0; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java index a154b83c88..6f6a7746b7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/DynamicValue.java @@ -17,19 +17,25 @@ package org.thingsboard.server.common.data.query; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import lombok.Getter; +import lombok.RequiredArgsConstructor; import java.io.Serializable; @Data +@RequiredArgsConstructor public class DynamicValue implements Serializable { @JsonIgnore private T resolvedValue; - @Getter private final DynamicValueSourceType sourceType; - @Getter private final String sourceAttribute; + private final boolean inherit; + + public DynamicValue(DynamicValueSourceType sourceType, String sourceAttribute) { + this.sourceAttribute = sourceAttribute; + this.sourceType = sourceType; + this.inherit = false; + } } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/UUIDConverterTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/UUIDConverterTest.java index adb173f367..0f5bf5f724 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/UUIDConverterTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/UUIDConverterTest.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import java.util.ArrayList; import java.util.Arrays; diff --git a/common/message/pom.xml b/common/message/pom.xml index 8bc2653bb0..06ba4aff3b 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -76,7 +76,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java index 6f7f01d62a..d9f05e3c84 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -39,6 +39,11 @@ public enum MsgType { */ COMPONENT_LIFE_CYCLE_MSG, + /** + * Special message to indicate rule node update request + */ + RULE_NODE_UPDATED_MSG, + /** * Misc messages consumed from the Queue and forwarded to Rule Engine Actor. * @@ -66,11 +71,6 @@ public enum MsgType { */ REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG, - /** - * Message that is sent by RuleActor implementation to RuleActor itself to log the error. - */ - RULE_TO_SELF_ERROR_MSG, - /** * Message that is sent by RuleActor implementation to RuleActor itself to process the message. */ diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java index fe35d47d88..b7ba251704 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorMsg.java @@ -22,4 +22,12 @@ public interface TbActorMsg { MsgType getMsgType(); + /** + * Executed when the target TbActor is stopped or destroyed. + * For example, rule node failed to initialize or removed from rule chain. + * Implementation should cleanup the resources. + */ + default void onTbActorStopped(TbActorStopReason reason) { + } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorStopReason.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorStopReason.java new file mode 100644 index 0000000000..32035a7352 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbActorStopReason.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg; + +public enum TbActorStopReason { + + INIT_FAILED, STOPPED + +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java new file mode 100644 index 0000000000..64bb772b4f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg; + +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@EqualsAndHashCode +public abstract class TbRuleEngineActorMsg implements TbActorMsg { + + @Getter + protected final TbMsg msg; + + public TbRuleEngineActorMsg(TbMsg msg) { + this.msg = msg; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/RuleNodeUpdatedMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/RuleNodeUpdatedMsg.java new file mode 100644 index 0000000000..ca4ab1c949 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/RuleNodeUpdatedMsg.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.plugin; + +import lombok.ToString; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.MsgType; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@ToString +public class RuleNodeUpdatedMsg extends ComponentLifecycleMsg { + + public RuleNodeUpdatedMsg(TenantId tenantId, EntityId entityId) { + super(tenantId, entityId, ComponentLifecycleEvent.UPDATED); + } + + @Override + public MsgType getMsgType() { + return MsgType.RULE_NODE_UPDATED_MSG; + } +} \ No newline at end of file diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java index ded2fa722c..7648fdab6d 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java @@ -15,32 +15,58 @@ */ package org.thingsboard.server.common.msg.queue; -import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; -import org.thingsboard.server.common.msg.TbActorMsg; +import org.thingsboard.server.common.msg.TbActorStopReason; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbRuleEngineActorMsg; -import java.io.Serializable; import java.util.Set; /** * Created by ashvayka on 15.03.18. */ -@Data -public final class QueueToRuleEngineMsg implements TbActorMsg { +@ToString +@EqualsAndHashCode(callSuper = true) +public final class QueueToRuleEngineMsg extends TbRuleEngineActorMsg { + @Getter private final TenantId tenantId; - private final TbMsg tbMsg; + @Getter private final Set relationTypes; + @Getter private final String failureMessage; + public QueueToRuleEngineMsg(TenantId tenantId, TbMsg tbMsg, Set relationTypes, String failureMessage) { + super(tbMsg); + this.tenantId = tenantId; + this.relationTypes = relationTypes; + this.failureMessage = failureMessage; + } + @Override public MsgType getMsgType() { return MsgType.QUEUE_TO_RULE_ENGINE_MSG; } + @Override + public void onTbActorStopped(TbActorStopReason reason) { + String message; + if (msg.getRuleChainId() != null) { + message = reason == TbActorStopReason.STOPPED ? + String.format("Rule chain [%s] stopped", msg.getRuleChainId().getId()) : + String.format("Failed to initialize rule chain [%s]!", msg.getRuleChainId().getId()); + } else { + message = reason == TbActorStopReason.STOPPED ? "Rule chain stopped" : "Failed to initialize rule chain!"; + } + msg.getCallback().onFailure(new RuleEngineException(message)); + } + public boolean isTellNext() { return relationTypes != null && !relationTypes.isEmpty(); } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java index 8e68d1f94c..cd073f3a6e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java @@ -24,6 +24,9 @@ import org.thingsboard.server.common.data.rule.RuleNode; @Slf4j public class RuleNodeException extends RuleEngineException { + + private static final long serialVersionUID = -1776681087370749776L; + @Getter private final String ruleChainName; @Getter @@ -33,6 +36,7 @@ public class RuleNodeException extends RuleEngineException { @Getter private final RuleNodeId ruleNodeId; + public RuleNodeException(String message, String ruleChainName, RuleNode ruleNode) { super(message); this.ruleChainName = ruleChainName; diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 4c3eb4aeba..e0b30dd654 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -124,7 +124,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java index 5b562cbfbf..525f9bfe92 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java @@ -154,6 +154,7 @@ public class TbServiceBusConsumerTemplate extends Abstract } private CompletableFuture> fromList(List> futures) { + @SuppressWarnings("unchecked") CompletableFuture>[] arrayFuture = new CompletableFuture[futures.size()]; futures.toArray(arrayFuture); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java index 0602dee3ae..1e5b90b5fe 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java @@ -22,7 +22,9 @@ import org.thingsboard.server.common.msg.queue.ServiceQueueKey; import java.util.Set; -public class ClusterTopologyChangeEvent extends ApplicationEvent { +public class ClusterTopologyChangeEvent extends TbApplicationEvent { + + private static final long serialVersionUID = -2441739930040282254L; @Getter private final Set serviceQueueKeys; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index d8164f4be8..2da438417a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -126,7 +126,7 @@ public class HashPartitionService implements PartitionService { } @Override - public void recalculatePartitions(ServiceInfo currentService, List otherServices) { + public synchronized void recalculatePartitions(ServiceInfo currentService, List otherServices) { logServiceInfo(currentService); otherServices.forEach(this::logServiceInfo); Map> queueServicesMap = new HashMap<>(); @@ -134,7 +134,7 @@ public class HashPartitionService implements PartitionService { for (ServiceInfo other : otherServices) { addNode(queueServicesMap, other); } - queueServicesMap.values().forEach(list -> list.sort((a, b) -> a.getServiceId().compareTo(b.getServiceId()))); + queueServicesMap.values().forEach(list -> list.sort(Comparator.comparing(ServiceInfo::getServiceId))); ConcurrentMap> oldPartitions = myPartitions; TenantId myIsolatedOrSystemTenantId = getSystemOrIsolatedTenantId(currentService); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java index e4edabbe19..2edcd2ceca 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java @@ -24,7 +24,9 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import java.util.Set; -public class PartitionChangeEvent extends ApplicationEvent { +public class PartitionChangeEvent extends TbApplicationEvent { + + private static final long serialVersionUID = -8731788167026510559L; @Getter private final ServiceQueueKey serviceQueueKey; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java new file mode 100644 index 0000000000..face2d36d6 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.discovery; + +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.concurrent.atomic.AtomicInteger; + +public class TbApplicationEvent extends ApplicationEvent { + + private static final long serialVersionUID = 3884264064887765146L; + + private static final AtomicInteger sequence = new AtomicInteger(); + + @Getter + private final int sequenceNumber; + + public TbApplicationEvent(Object source) { + super(source); + sequenceNumber = sequence.incrementAndGet(); + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java new file mode 100644 index 0000000000..9158d8f0c8 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.discovery; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationListener; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public abstract class TbApplicationEventListener implements ApplicationListener { + + private int lastProcessedSequenceNumber = Integer.MIN_VALUE; + private final Lock seqNumberLock = new ReentrantLock(); + + @Override + public void onApplicationEvent(T event) { + boolean validUpdate = false; + seqNumberLock.lock(); + try { + if (event.getSequenceNumber() > lastProcessedSequenceNumber) { + validUpdate = true; + lastProcessedSequenceNumber = event.getSequenceNumber(); + } + } finally { + seqNumberLock.unlock(); + } + if (validUpdate) { + onTbApplicationEvent(event); + } else { + log.info("Application event ignored due to invalid sequence number ({} > {}). Event: {}", lastProcessedSequenceNumber, event.getSequenceNumber(), event); + } + } + + protected abstract void onTbApplicationEvent(T event); + + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatisticConfig.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatisticConfig.java new file mode 100644 index 0000000000..bfe7d29bb6 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatisticConfig.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.kafka; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka") +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class TbKafkaConsumerStatisticConfig { + @Value("${queue.kafka.consumer-stats.enabled:true}") + private Boolean enabled; + @Value("${queue.kafka.consumer-stats.print-interval-ms:60000}") + private Long printIntervalMs; + @Value("${queue.kafka.consumer-stats.kafka-response-timeout-ms:1000}") + private Long kafkaResponseTimeoutMs; +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java new file mode 100644 index 0000000000..0ccf9ba42c --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java @@ -0,0 +1,184 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.kafka; + +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.admin.AdminClient; +import org.apache.kafka.clients.consumer.Consumer; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.consumer.OffsetAndMetadata; +import org.apache.kafka.common.TopicPartition; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.queue.discovery.PartitionService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka") +public class TbKafkaConsumerStatsService { + private final Set monitoredGroups = ConcurrentHashMap.newKeySet(); + + private final TbKafkaSettings kafkaSettings; + private final TbKafkaConsumerStatisticConfig statsConfig; + private final PartitionService partitionService; + + private AdminClient adminClient; + private Consumer consumer; + private ScheduledExecutorService statsPrintScheduler; + + @PostConstruct + public void init() { + if (!statsConfig.getEnabled()) { + return; + } + this.adminClient = AdminClient.create(kafkaSettings.toAdminProps()); + this.statsPrintScheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("kafka-consumer-stats")); + + Properties consumerProps = kafkaSettings.toConsumerProps(); + consumerProps.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumer-stats-loader-client"); + consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-stats-loader-client-group"); + this.consumer = new KafkaConsumer<>(consumerProps); + + startLogScheduling(); + } + + private void startLogScheduling() { + Duration timeoutDuration = Duration.ofMillis(statsConfig.getKafkaResponseTimeoutMs()); + statsPrintScheduler.scheduleWithFixedDelay(() -> { + if (!isStatsPrintRequired()) { + return; + } + for (String groupId : monitoredGroups) { + try { + Map groupOffsets = adminClient.listConsumerGroupOffsets(groupId).partitionsToOffsetAndMetadata() + .get(statsConfig.getKafkaResponseTimeoutMs(), TimeUnit.MILLISECONDS); + Map endOffsets = consumer.endOffsets(groupOffsets.keySet(), timeoutDuration); + + List lagTopicsStats = getTopicsStatsWithLag(groupOffsets, endOffsets); + if (!lagTopicsStats.isEmpty()) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < lagTopicsStats.size(); i++) { + builder.append(lagTopicsStats.get(i).toString()); + if (i != lagTopicsStats.size() - 1) { + builder.append(", "); + } + } + log.info("[{}] Topic partitions with lag: [{}].", groupId, builder.toString()); + } + } catch (Exception e) { + log.warn("[{}] Failed to get consumer group stats. Reason - {}.", groupId, e.getMessage()); + log.trace("Detailed error: ", e); + } + } + + }, statsConfig.getPrintIntervalMs(), statsConfig.getPrintIntervalMs(), TimeUnit.MILLISECONDS); + } + + private boolean isStatsPrintRequired() { + boolean isMyRuleEnginePartition = partitionService.resolve(ServiceType.TB_RULE_ENGINE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); + boolean isMyCorePartition = partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); + return log.isInfoEnabled() && (isMyRuleEnginePartition || isMyCorePartition); + } + + private List getTopicsStatsWithLag(Map groupOffsets, Map endOffsets) { + List consumerGroupStats = new ArrayList<>(); + for (TopicPartition topicPartition : groupOffsets.keySet()) { + long endOffset = endOffsets.get(topicPartition); + long committedOffset = groupOffsets.get(topicPartition).offset(); + long lag = endOffset - committedOffset; + if (lag != 0) { + GroupTopicStats groupTopicStats = GroupTopicStats.builder() + .topic(topicPartition.topic()) + .partition(topicPartition.partition()) + .committedOffset(committedOffset) + .endOffset(endOffset) + .lag(lag) + .build(); + consumerGroupStats.add(groupTopicStats); + } + } + return consumerGroupStats; + } + + public void registerClientGroup(String groupId) { + if (statsConfig.getEnabled() && !StringUtils.isEmpty(groupId)) { + monitoredGroups.add(groupId); + } + } + + public void unregisterClientGroup(String groupId) { + if (statsConfig.getEnabled() && !StringUtils.isEmpty(groupId)) { + monitoredGroups.remove(groupId); + } + } + + @PreDestroy + public void destroy() { + if (statsPrintScheduler != null) { + statsPrintScheduler.shutdownNow(); + } + if (adminClient != null) { + adminClient.close(); + } + if (consumer != null) { + consumer.close(); + } + } + + + @Builder + @Data + private static class GroupTopicStats { + private String topic; + private int partition; + private long committedOffset; + private long endOffset; + private long lag; + + @Override + public String toString() { + return "[" + + "topic=[" + topic + ']' + + ", partition=[" + partition + "]" + + ", committedOffset=[" + committedOffset + "]" + + ", endOffset=[" + endOffset + "]" + + ", lag=[" + lag + "]" + + "]"; + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java index 8743f4d6cc..b1b3d5ce05 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java @@ -42,10 +42,13 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue private final KafkaConsumer consumer; private final TbKafkaDecoder decoder; + private final TbKafkaConsumerStatsService statsService; + private final String groupId; + @Builder private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder, String clientId, String groupId, String topic, - TbQueueAdmin admin) { + TbQueueAdmin admin, TbKafkaConsumerStatsService statsService) { super(topic); Properties props = settings.toConsumerProps(); props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); @@ -53,6 +56,13 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); } + this.statsService = statsService; + this.groupId = groupId; + + if (statsService != null) { + statsService.registerClientGroup(groupId); + } + this.admin = admin; this.consumer = new KafkaConsumer<>(props); this.decoder = decoder; @@ -96,6 +106,8 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue consumer.unsubscribe(); consumer.close(); } + if (statsService != null) { + statsService.unregisterClientGroup(groupId); + } } - } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 4c35c0ca1f..86738acea8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -22,6 +22,10 @@ import org.apache.kafka.clients.CommonClientConfigs; import org.apache.kafka.clients.admin.AdminClientConfig; import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.serialization.ByteArrayDeserializer; +import org.apache.kafka.common.serialization.ByteArraySerializer; +import org.apache.kafka.common.serialization.StringDeserializer; +import org.apache.kafka.common.serialization.StringSerializer; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -107,8 +111,8 @@ public class TbKafkaSettings { props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, fetchMaxBytes); props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs); - props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer"); - props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer"); + props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); + props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class); return props; } @@ -120,8 +124,8 @@ public class TbKafkaSettings { props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize); props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs); props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); - props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer"); - props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer"); + props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); + props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); return props; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java index b46fd4c87d..226ed32682 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java @@ -60,6 +60,7 @@ public final class InMemoryStorage { public List get(String topic) throws InterruptedException { if (storage.containsKey(topic)) { List entities; + @SuppressWarnings("unchecked") T first = (T) storage.get(topic).poll(); if (first != null) { entities = new ArrayList<>(); @@ -67,7 +68,9 @@ public final class InMemoryStorage { List otherList = new ArrayList<>(); storage.get(topic).drainTo(otherList, 999); for (TbQueueMsg other : otherList) { - entities.add((T) other); + @SuppressWarnings("unchecked") + T entity = (T) other; + entities.add(entity); } } else { entities = Collections.emptyList(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java index 95af725bb4..6e28d48a4b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java @@ -64,6 +64,7 @@ public class InMemoryTbQueueConsumer implements TbQueueCon @Override public List poll(long durationInMillis) { if (subscribed) { + @SuppressWarnings("unchecked") List messages = partitions .stream() .map(tpi -> { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index eebffc37b0..e07f3e43d3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -39,6 +39,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerStatsService; import org.thingsboard.server.queue.kafka.TbKafkaConsumerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaSettings; @@ -65,6 +66,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; @@ -79,6 +81,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbKafkaConsumerStatsService consumerStatsService, TbKafkaTopicConfigs kafkaTopicConfigs) { this.partitionService = partitionService; this.kafkaSettings = kafkaSettings; @@ -88,6 +91,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; this.jsInvokeSettings = jsInvokeSettings; + this.consumerStatsService = consumerStatsService; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); @@ -156,6 +160,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("re-" + queueName + "-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(ruleEngineAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -168,6 +173,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("monolith-rule-engine-notifications-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(notificationAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -180,6 +186,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("monolith-core-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(coreAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -192,6 +199,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("monolith-core-notifications-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(notificationAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -204,6 +212,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("monolith-transport-api-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(transportApiAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -237,6 +246,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders()); } ); + responseBuilder.statsService(consumerStatsService); responseBuilder.admin(jsExecutorAdmin); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder @@ -259,6 +269,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.groupId("monolith-us-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(coreAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index 15771a3b78..24e80adec5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -39,6 +39,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerStatsService; import org.thingsboard.server.queue.kafka.TbKafkaConsumerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaSettings; @@ -62,6 +63,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; @@ -75,6 +77,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { TbQueueRuleEngineSettings ruleEngineSettings, TbQueueTransportApiSettings transportApiSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbKafkaConsumerStatsService consumerStatsService, TbKafkaTopicConfigs kafkaTopicConfigs) { this.partitionService = partitionService; this.kafkaSettings = kafkaSettings; @@ -83,6 +86,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { this.ruleEngineSettings = ruleEngineSettings; this.transportApiSettings = transportApiSettings; this.jsInvokeSettings = jsInvokeSettings; + this.consumerStatsService = consumerStatsService; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); @@ -150,6 +154,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { consumerBuilder.groupId("tb-core-node"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(coreAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -162,6 +167,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { consumerBuilder.groupId("tb-core-notifications-node-" + serviceInfoProvider.getServiceId()); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(notificationAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -174,6 +180,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { consumerBuilder.groupId("tb-core-transport-api-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(transportApiAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -208,6 +215,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { } ); responseBuilder.admin(jsExecutorAdmin); + responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> builder = DefaultTbQueueRequestTemplate.builder(); @@ -229,6 +237,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { consumerBuilder.groupId("tb-core-us-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(coreAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index b45ec62b04..a8247dc11e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -37,6 +37,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerStatsService; import org.thingsboard.server.queue.kafka.TbKafkaConsumerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaSettings; @@ -59,6 +60,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { private final TbQueueCoreSettings coreSettings; private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; @@ -70,6 +72,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbKafkaConsumerStatsService consumerStatsService, TbKafkaTopicConfigs kafkaTopicConfigs) { this.partitionService = partitionService; this.kafkaSettings = kafkaSettings; @@ -77,6 +80,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { this.coreSettings = coreSettings; this.ruleEngineSettings = ruleEngineSettings; this.jsInvokeSettings = jsInvokeSettings; + this.consumerStatsService = consumerStatsService; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); @@ -145,6 +149,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { consumerBuilder.groupId("re-" + queueName + "-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(ruleEngineAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -157,6 +162,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { consumerBuilder.groupId("tb-rule-engine-notifications-node-" + serviceInfoProvider.getServiceId()); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(notificationAdmin); + consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -181,6 +187,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { } ); responseBuilder.admin(jsExecutorAdmin); + responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> builder = DefaultTbQueueRequestTemplate.builder(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java index 0c64a57304..8c0c6999d5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java @@ -32,6 +32,7 @@ import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerStatsService; import org.thingsboard.server.queue.kafka.TbKafkaConsumerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaSettings; @@ -54,6 +55,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; + private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; @@ -66,6 +68,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { TbQueueRuleEngineSettings ruleEngineSettings, TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, + TbKafkaConsumerStatsService consumerStatsService, TbKafkaTopicConfigs kafkaTopicConfigs) { this.kafkaSettings = kafkaSettings; this.serviceInfoProvider = serviceInfoProvider; @@ -73,6 +76,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { this.ruleEngineSettings = ruleEngineSettings; this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; + this.consumerStatsService = consumerStatsService; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); @@ -95,6 +99,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { responseBuilder.groupId("transport-node-" + serviceInfoProvider.getServiceId()); responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); responseBuilder.admin(transportApiAdmin); + responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); @@ -136,6 +141,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { responseBuilder.groupId("transport-node-" + serviceInfoProvider.getServiceId()); responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); responseBuilder.admin(notificationAdmin); + responseBuilder.statsService(consumerStatsService); return responseBuilder.build(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 066475d5ba..01a322f158 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -47,6 +47,7 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { @Value("${usage.stats.report.interval:10}") private int interval; + @SuppressWarnings("unchecked") private final ConcurrentMap[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; private final PartitionService partitionService; private final SchedulerComponent scheduler; diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 205f5c1dc6..005c210b7b 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -79,7 +79,7 @@ org.mockito - mockito-all + mockito-core test @@ -89,4 +89,4 @@ - \ No newline at end of file + diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index a8a1ac452a..cea8ddc0ce 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -80,7 +80,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index e31fb606f2..9332502207 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -25,6 +25,7 @@ import org.eclipse.californium.core.network.Endpoint; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.californium.elements.EndpointContext; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; @@ -264,12 +265,15 @@ public class CoapTransportResource extends CoapResource { } private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { - String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + EndpointContext sourceContext = request.getSourceContext(); + String token = sourceContext.getPeerAddress().getAddress().getHostAddress() + ":" + sourceContext.getPeerAddress().getPort() + ":" + request.getTokenString(); return tokenToSessionIdMap.remove(token); + } private String registerAsyncCoapSession(CoapExchange exchange, Request request, TransportProtos.SessionInfoProto sessionInfo, UUID sessionId) { - String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); + EndpointContext sourceContext = request.getSourceContext(); + String token = sourceContext.getPeerAddress().getAddress().getHostAddress() + ":" + sourceContext.getPeerAddress().getPort() + ":" + request.getTokenString(); tokenToSessionIdMap.putIfAbsent(token, sessionInfo); CoapSessionListener attrListener = new CoapSessionListener(sessionId, exchange); transportService.registerAsyncSession(sessionInfo, attrListener); diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 2c8a9bbeae..f229627480 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -73,7 +73,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 00d559c9d1..83986a953e 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -107,7 +107,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index ed7d1d9da1..68fdc94dac 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -25,20 +25,18 @@ import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuil import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportContextServer; import java.math.BigInteger; import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; @@ -47,16 +45,17 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; @Slf4j @@ -65,6 +64,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandle public class LwM2MTransportBootstrapServerConfiguration { private PublicKey publicKey; private PrivateKey privateKey; + private boolean pskMode = false; @Autowired private LwM2MTransportContextBootstrap contextBs; @@ -78,61 +78,53 @@ public class LwM2MTransportBootstrapServerConfiguration { @Autowired private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; - @Primary - @Bean(name = "leshanBootstrapX509") - @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_x509:false}'=='true')") - public LeshanBootstrapServer getLeshanBootstrapServerX509() { - log.info("Prepare and start BootstrapServerX509... PostConstruct"); - return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecX509(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortX509(), X509); - } - - @Bean(name = "leshanBootstrapPsk") - @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_psk:false}'=='true')") - public LeshanBootstrapServer getLeshanBootstrapServerPsk() { - log.info("Prepare and start BootstrapServerRsk... PostConstruct"); - return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecPsk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortPsk(), PSK); - } - @Bean(name = "leshanBootstrapRpk") - @ConditionalOnExpression("('${transport.lwm2m.bootstrap.secure.start_rpk:false}'=='true')") - public LeshanBootstrapServer getLeshanBootstrapServerRpk() { - log.info("Prepare and start BootstrapServerRpk... PostConstruct"); - return getLeshanBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSecRpk(), this.contextBs.getCtxBootStrap().getBootstrapSecurePortRpk(), RPK); + @Bean + public LeshanBootstrapServer getLeshanBootstrapServer() { + log.info("Prepare and start BootstrapServer... PostConstruct"); + return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); } - public LeshanBootstrapServer getLeshanBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort, LwM2MSecurityMode dtlsMode) { + public LeshanBootstrapServer getLhBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort) { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec); - builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapSecureHost(), bootstrapSecurePort); + builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapHostSecurity(), bootstrapSecurePort); /** Create CoAP Config */ - builder.setCoapConfig(getCoapConfig (bootstrapPortNoSec, bootstrapSecurePort)); + builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort)); - /** ConfigStore */ + /** Define model provider (Create Models )*/ + builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue())); + + /** Create credentials */ + this.setServerWithCredentials(builder); + + /** Set securityStore with new ConfigStore */ builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); /** SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); - /** Define model provider (Create Models )*/ - builder.setModel(new StaticModel(contextS.getCtxServer().getModelsValue())); - - /** Create credentials */ - LwM2MSetSecurityStoreBootstrap(builder, dtlsMode); /** Create and Set DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - if (dtlsMode==PSK) { - dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers()); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getCtxServer().isRecommendedSupportedGroups()); - dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getCtxServer().isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers()); + if (this.pskMode) { + dtlsConfig.setSupportedCipherSuites( + TLS_PSK_WITH_AES_128_CCM_8, + TLS_PSK_WITH_AES_128_CBC_SHA256); } - else { - dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getCtxServer().isRecommendedCiphers()); -// dtlsConfig.setRecommendedSupportedGroupsOnly(false); + else { + dtlsConfig.setSupportedCipherSuites( + TLS_PSK_WITH_AES_128_CCM_8, + TLS_PSK_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); } - builder.setDtlsConfig(dtlsConfig); + /** Set DTLS Config */ + builder.setDtlsConfig(dtlsConfig); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); builder.setSessionManager(sessionManager); @@ -141,150 +133,157 @@ public class LwM2MTransportBootstrapServerConfiguration { return builder.build(); } - public void LwM2MSetSecurityStoreBootstrap(LeshanBootstrapServerBuilder builder, LwM2MSecurityMode dtlsMode) { - - /** Set securityStore with new registrationStore */ - - switch (dtlsMode) { - /** Use No_Sec only */ - case NO_SEC: - setServerWithX509Cert(builder, NO_SEC.code); - break; - /** Use PSK/RPK */ - case PSK: - break; - case RPK: - setRPK(builder); - break; - case X509: - setServerWithX509Cert(builder, X509.code); - break; - /** Use X509_EST only */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - /** Use ather X509, PSK, No_Sec ?? */ - default: - break; - } - } - - private void setRPK(LeshanBootstrapServerBuilder builder) { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.contextBs.getCtxBootStrap().getBootstrapPrivateS() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); - builder.setPrivateKey(this.privateKey); - this.contextBs.getCtxBootStrap().setBootstrapPublicKey(this.publicKey); - getParamsRPK(); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void setServerWithX509Cert(LeshanBootstrapServerBuilder builder, int securityModeCode) { + private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { try { if (this.contextS.getCtxServer().getKeyStoreValue() != null) { KeyStore keyStoreServer = this.contextS.getCtxServer().getKeyStoreValue(); - setBuilderX509(builder); - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null && securityModeCode == X509.code) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); + if (this.setBuilderX509(builder)) { + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } } - } - else { + } else if (this.setServerRPK(builder)) { + this.infoPramsUri("RPK"); + this.infoParamsBootstrapServerKey(this.publicKey, this.privateKey); + } else { /** by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for BootStrapServer"); + log.info("Unable to load X509 files for BootStrapServer"); + this.pskMode = true; + this.infoPramsUri("PSK"); } } catch (KeyStoreException ex) { log.error("[{}] Unable to load X509 files server", ex.getMessage()); } - } - private void setBuilderX509(LeshanBootstrapServerBuilder builder) { + private boolean setBuilderX509(LeshanBootstrapServerBuilder builder) { /** * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks */ try { X509Certificate serverCertificate = (X509Certificate) this.contextS.getCtxServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); - this.privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); - if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPrivateKey(this.privateKey); - } - if (serverCertificate != null) { + PrivateKey privateKey = (PrivateKey) this.contextS.getCtxServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getCtxServer().getKeyStorePasswordServer() == null ? null : this.contextS.getCtxServer().getKeyStorePasswordServer().toCharArray()); + PublicKey publicKey = serverCertificate.getPublicKey(); + if (serverCertificate != null && + privateKey != null && privateKey.getEncoded().length > 0 && + publicKey != null && publicKey.getEncoded().length > 0) { + builder.setPublicKey(serverCertificate.getPublicKey()); + builder.setPrivateKey(privateKey); builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - this.infoParamsX509(serverCertificate); + this.infoParamsServerX509(serverCertificate, publicKey, privateKey); + return true; + } else { + return false; } } catch (Exception ex) { log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + return false; } } - private void getParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nBootstrap uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); + private void infoParamsServerX509(X509Certificate certificate, PublicKey publicKey, PrivateKey privateKey) { + try { + this.infoPramsUri("X509"); + log.info("\n- X509 Certificate (Hex): [{}]", + Hex.encodeHexString(certificate.getEncoded())); + this.infoParamsBootstrapServerKey(publicKey, privateKey); + } catch (CertificateEncodingException e) { + log.error("", e); } } - private void infoParamsX509(X509Certificate certificate) { + private void infoPramsUri(String mode) { + log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", + mode, + this.contextBs.getCtxBootStrap().getBootstrapHost() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), + this.contextBs.getCtxBootStrap().getBootstrapHostSecurity() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); + } + + + private boolean setServerRPK(LeshanBootstrapServerBuilder builder) { try { - log.info("BootStrap uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", - Hex.encodeHexString(certificate.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } catch (CertificateEncodingException e) { - log.error("", e); + this.generateKeyForBootstrapRPK(); + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); +// builder.setCertificateChain(new X509Certificate[] { serverCertificate }); + /// Trust all certificates. + builder.setTrustedCertificates(new X509Certificate[0]); + builder.setPrivateKey(this.privateKey); + return true; + } + } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { + log.error("Fail create Bootstrap Server with RPK", e); } + return false; } + /** + * From yml: bootstrap + * public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" + * public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" + * private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" + */ + private void generateKeyForBootstrapRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get public key */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().isEmpty()) { + /** Get private key */ + byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().toCharArray()); + try { + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); + } catch (InvalidKeySpecException ignore2) { + log.error("Invalid Bootstrap Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded()); + } + } + } + + private void infoParamsBootstrapServerKey(PublicKey publicKey, PrivateKey privateKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) publicKey).getParams().toString(); + String privHex = Hex.encodeHexString(privateKey.getEncoded()); + log.info("\n- Public Key (Hex): [{}] \n" + + "- Private Key (Hex): [{}], \n" + + "public_x: \"${LWM2M_SERVER_PUBLIC_X_BS:{}}\" \n" + + "public_y: \"${LWM2M_SERVER_PUBLIC_Y_BS:{}}\" \n" + + "private_encoded: \"${LWM2M_SERVER_PRIVATE_ENCODED_BS:{}}\" \n" + + "- Elliptic Curve parameters : [{}]", + Hex.encodeHexString(publicKey.getEncoded()), + privHex, + Hex.encodeHexString(x), + Hex.encodeHexString(y), + privHex, + params); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java index 6ca751b138..c3d6d9d0c1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -31,39 +30,20 @@ import javax.annotation.PreDestroy; public class LwM2MTransportBootstrapServerInitializer { @Autowired(required = false) - @Qualifier("leshanBootstrapX509") - private LeshanBootstrapServer lhBServerCert; - - @Autowired(required = false) - @Qualifier("leshanBootstrapPsk") - private LeshanBootstrapServer lhBServerPsk; - - @Autowired(required = false) - @Qualifier("leshanBootstrapRpk") - private LeshanBootstrapServer lhBServerRpk; + private LeshanBootstrapServer lhBServer; @Autowired private LwM2MTransportContextBootstrap contextBS; @PostConstruct public void init() { - if (this.contextBS.getCtxBootStrap().getBootstrapStartPsk()) { - this.lhBServerPsk.start(); - } - if (this.contextBS.getCtxBootStrap().getBootstrapStartRpk()) { - this.lhBServerRpk.start(); - } - if (this.contextBS.getCtxBootStrap().getBootstrapStartX509()) { - this.lhBServerCert.start(); - } + this.lhBServer.start(); } @PreDestroy public void shutdown() throws InterruptedException { log.info("Stopping LwM2M transport Bootstrap Server!"); - lhBServerPsk.destroy(); - lhBServerRpk.destroy(); - lhBServerCert.destroy(); + lhBServer.destroy(); log.info("LwM2M transport Bootstrap Server stopped!"); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 0b56490179..eba61af24c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; -import lombok.Builder; import lombok.Data; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.request.BindingMode; @@ -40,7 +39,6 @@ public class LwM2MBootstrapConfig { * notifIfDisabled: boolean, * binding: string * */ - @Builder.Default LwM2MBootstrapServers servers; /** -bootstrapServer, lwm2mServer diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index e68cddc3be..35a1406b83 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -166,13 +166,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); - String logMsg = String.format(LOG_LW2M_INFO + ": getParametersBootstrap: %s Access connect client with bootstrap server.", store.getEndPoint()); + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint()); context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); return lwM2MBootstrapConfig; } else { log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - log.error(LOG_LW2M_ERROR + " getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); - String logMsg = String.format(LOG_LW2M_ERROR + ": getParametersBootstrap: %s Different values SecurityMode between of client and profile.", store.getEndPoint()); + log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); + String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); context.sentParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); return null; } @@ -188,6 +188,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { /** * Bootstrap security have to sync between (bootstrapServer in credential and bootstrapServer in profile) * and (lwm2mServer in credential and lwm2mServer in profile + * * @param bootstrapFromCredential - Bootstrap -> Security of bootstrapServer in credential * @param profileServerBootstrap - Bootstrap -> Security of bootstrapServer in profile * @param lwm2mFromCredential - Bootstrap -> Security of lwm2mServer in credential diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java index 801311fd85..e3757701cb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapServers.java @@ -15,19 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; -import lombok.Builder; import lombok.Data; @Data public class LwM2MBootstrapServers { - @Builder.Default private Integer shortId = 123; - @Builder.Default private Integer lifetime = 300; - @Builder.Default private Integer defaultMinPeriod = 1; - @Builder.Default private boolean notifIfDisabled = true; - @Builder.Default private String binding = "U"; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index be8d9f34b0..9dca6057da 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; -import lombok.Builder; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; @@ -24,28 +23,18 @@ import org.eclipse.leshan.core.SecurityMode; @Data public class LwM2MServerBootstrap { - @Builder.Default String clientPublicKeyOrId = ""; - @Builder.Default String clientSecretKey = ""; - @Builder.Default String serverPublicKey = ""; - @Builder.Default Integer clientHoldOffTime = 1; - @Builder.Default Integer bootstrapServerAccountTimeout = 0; - @Builder.Default String host = "0.0.0.0"; - @Builder.Default Integer port = 0; - @Builder.Default String securityMode = SecurityMode.NO_SEC.name(); - @Builder.Default Integer serverId = 123; - @Builder.Default boolean bootstrapServerIs = false; public LwM2MServerBootstrap(){}; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 3ba6daee7a..34497069f5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -25,6 +25,7 @@ import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; import java.util.Arrays; +import java.util.Collections; import java.util.List; @Slf4j @@ -49,10 +50,11 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession this.securityChecker = securityChecker; } + @SuppressWarnings("deprecation") public BootstrapSession begin(String endpoint, Identity clientIdentity) { boolean authorized; if (bsSecurityStore != null) { - List securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Arrays.asList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint); + List securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint); log.info("Bootstrap session started securityInfos: [{}]", securityInfos); authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index f4abbd2fda..4df520543b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -103,18 +103,18 @@ public class LWM2MGenerationPSkRPkECC { /** Get Curves params */ String privHex = Hex.encodeHexString(privKey.getEncoded()); log.info("\nCreating new RPK for the next start... \n" + - " Elliptic Curve parameters : [{}] \n" + + " Public Key (Hex): [{}]\n" + + " Private Key (Hex): [{}]" + " public_x : [{}] \n" + " public_y : [{}] \n" + - " private_s : [{}] \n" + - " Public Key (Hex): [{}]\n" + - " Private Key (Hex): [{}]", - ecPublicKey.getParams().toString(), + " private_encode : [{}] \n" + + " Elliptic Curve parameters : [{}] \n", + Hex.encodeHexString(pubKey.getEncoded()), + privHex, Hex.encodeHexString(x), Hex.encodeHexString(y), - privHex.substring(privHex.length() - 64), - Hex.encodeHexString(pubKey.getEncoded()), - Hex.encodeHexString(privKey.getEncoded())); + privHex, + ecPublicKey.getParams().toString()); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java index e417a1c292..d12247467f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java @@ -16,7 +16,6 @@ package org.thingsboard.server.transport.lwm2m.secure; import com.google.gson.JsonObject; -import lombok.Builder; import lombok.Data; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.security.SecurityInfo; @@ -29,7 +28,6 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DE public class ReadResultSecurityStore { private ValidateDeviceCredentialsResponseMsg msg; private SecurityInfo securityInfo; - @Builder.Default private int securityMode = DEFAULT_MODE.code; /** bootstrap */ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java index 249ee78344..878f7c8542 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportHandler.java @@ -36,7 +36,7 @@ import org.nustaq.serialization.FSTConfiguration; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientProfile; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; import java.io.File; @@ -70,7 +70,8 @@ public class LwM2MTransportHandler { public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; - public static final String KEYNAME = "keyName"; + public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; + public static final String KEY_NAME = "keyName"; public static final String OBSERVE = "observe"; public static final String BOOTSTRAP = "bootstrap"; public static final String SERVERS = "servers"; @@ -187,18 +188,22 @@ public class LwM2MTransportHandler { return null; } - public static AttrTelemetryObserveValue getNewProfileParameters(JsonObject profilesConfigData) { - AttrTelemetryObserveValue attrTelemetryObserveValue = new AttrTelemetryObserveValue(); - attrTelemetryObserveValue.setPostKeyNameProfile(profilesConfigData.get(KEYNAME).getAsJsonObject()); - attrTelemetryObserveValue.setPostAttributeProfile(profilesConfigData.get(ATTRIBUTE).getAsJsonArray()); - attrTelemetryObserveValue.setPostTelemetryProfile(profilesConfigData.get(TELEMETRY).getAsJsonArray()); - attrTelemetryObserveValue.setPostObserveProfile(profilesConfigData.get(OBSERVE).getAsJsonArray()); - return attrTelemetryObserveValue; + public static LwM2MClientProfile getNewProfileParameters(JsonObject profilesConfigData) { + LwM2MClientProfile lwM2MClientProfile = new LwM2MClientProfile(); + lwM2MClientProfile.setPostClientLwM2mSettings(profilesConfigData.get(CLIENT_LWM2M_SETTINGS).getAsJsonObject()); + lwM2MClientProfile.setPostKeyNameProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).getAsJsonObject()); + lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray()); + lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray()); + lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).getAsJsonArray()); + return lwM2MClientProfile; } /** * @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard - * Example: with pathResource (use only pathResource) + * Example: + * property: {"clientLwM2mSettings": { + * clientUpdateValueAfterConnect: false; + * } * property: "observeAttr" * {"keyName": { * "/3/0/1": "modelNumber", @@ -209,15 +214,14 @@ public class LwM2MTransportHandler { * "telemetry":["/1/0/1","/2/0/1","/6/0/1"], * "observe":["/2/0","/2/0/0","/4/0/2"]} */ - public static JsonObject getObserveAttrTelemetryFromThingsboard(DeviceProfile deviceProfile) { + public static LwM2MClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { -// Lwm2mDeviceProfileTransportConfiguration lwm2mDeviceProfileTransportConfiguration = (Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - Object observeAttr = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); + Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); try { ObjectMapper mapper = new ObjectMapper(); - String observeAttrStr = mapper.writeValueAsString(observeAttr); - JsonObject objectMsg = (observeAttrStr != null) ? validateJson(observeAttrStr) : null; - return (getValidateCredentialsBodyFromThingsboard(objectMsg)) ? objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject() : null; + String profileStr = mapper.writeValueAsString(profile); + JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; + return (getValidateCredentialsBodyFromThingsboard(profileJson)) ? LwM2MTransportHandler.getNewProfileParameters(profileJson) : null; } catch (IOException e) { log.error("", e); } @@ -240,15 +244,28 @@ public class LwM2MTransportHandler { return null; } + public static boolean getClientUpdateValueAfterConnect (LwM2MClientProfile profile) { + return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientUpdateValueAfterConnect") && + profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientUpdateValueAfterConnect").getAsBoolean(); + } + + public static boolean getClientOnlyObserveAfterConnect (LwM2MClientProfile profile) { + return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientOnlyObserveAfterConnect") && + profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientOnlyObserveAfterConnect").getAsBoolean(); + } + private static boolean getValidateCredentialsBodyFromThingsboard(JsonObject objectMsg) { return (objectMsg != null && !objectMsg.isJsonNull() && + objectMsg.has(CLIENT_LWM2M_SETTINGS) && + !objectMsg.get(CLIENT_LWM2M_SETTINGS).isJsonNull() && + objectMsg.get(CLIENT_LWM2M_SETTINGS).isJsonObject() && objectMsg.has(OBSERVE_ATTRIBUTE_TELEMETRY) && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonNull() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonObject() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(KEYNAME) && - !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEYNAME).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEYNAME).isJsonObject() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(KEY_NAME) && + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).isJsonNull() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).isJsonObject() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE) && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonNull() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonArray() && @@ -295,6 +312,7 @@ public class LwM2MTransportHandler { return object; } + @SuppressWarnings("unchecked") public static Optional decode(byte[] byteArray) { try { FSTConfiguration config = FSTConfiguration.createDefaultConfiguration(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java index 1c4590ace8..5b499bc892 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportRequest.java @@ -39,7 +39,6 @@ import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.LwM2mResponse; -import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.WriteAttributesResponse; @@ -96,7 +95,7 @@ public class LwM2MTransportRequest { this.converter = LwM2mValueConverterImpl.getInstance(); executorResponse = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); - executorResponseError = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestErrorPoolSize(), + executorResponseError = Executors.newFixedThreadPool(this.context.getCtxServer().getRequestErrorPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel response Error", RESPONSE_CHANNEL))); } @@ -112,16 +111,15 @@ public class LwM2MTransportRequest { /** * Device management and service enablement, including Read, Write, Execute, Discover, Create, Delete and Write-Attributes * - * @param lwServer - * @param registration - * @param target - * @param typeOper - * @param contentFormatParam - * @param lwM2MClient - * @param observation + * @param lwServer - + * @param registration - + * @param target - + * @param typeOper - + * @param contentFormatParam - + * @param observation - */ - public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, String contentFormatParam, - LwM2MClient lwM2MClient, Observation observation, Object params, long timeoutInMs, boolean isDelayedUpdate) { + public void sendAllRequest(LeshanServer lwServer, Registration registration, String target, String typeOper, + String contentFormatParam, Observation observation, Object params, long timeoutInMs) { LwM2mPath resultIds = new LwM2mPath(target); if (registration != null && resultIds.getObjectId() >= 0) { DownlinkRequest request = null; @@ -221,13 +219,7 @@ public class LwM2MTransportRequest { } if (request != null) { - this.sendRequest(lwServer, registration, request, lwM2MClient, timeoutInMs, isDelayedUpdate); - } else if (isDelayedUpdate) { - String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg No SendRequest to Client", target); - service.sentLogsToThingsboard(msg, registration); - log.error("[{}] - [{}] No SendRequest", target); - this.handleResponseError(registration, target, lwM2MClient, isDelayedUpdate); - + this.sendRequest(lwServer, registration, request, timeoutInMs); } } } @@ -237,45 +229,42 @@ public class LwM2MTransportRequest { * @param lwServer - * @param registration - * @param request - - * @param lwM2MClient - * @param timeoutInMs - */ - private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, LwM2MClient lwM2MClient, long timeoutInMs, boolean isDelayedUpdate) { + @SuppressWarnings("unchecked") + private void sendRequest(LeshanServer lwServer, Registration registration, DownlinkRequest request, long timeoutInMs) { + LwM2MClient lwM2MClient = this.service.lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); lwServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { + if (!lwM2MClient.isInit()) { + lwM2MClient.initValue(this.service, request.getPath().toString()); + } if (isSuccess(((Response) response.getCoapResponse()).getCode())) { - this.handleResponse(registration, request.getPath().toString(), response, request, lwM2MClient, isDelayedUpdate); + this.handleResponse(registration, request.getPath().toString(), response, request); if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { - String delayedUpdateStr = ""; - if (isDelayedUpdate) { - delayedUpdateStr = " (delayedUpdate) "; - } - String msg = String.format(LOG_LW2M_INFO + ": sendRequest Replace%s: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", - delayedUpdateStr, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), + String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", + LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString()); service.sentLogsToThingsboard(msg, registration); - log.info("[{}] - [{}] [{}] [{}] Update SendRequest[{}]", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), - ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue(), delayedUpdateStr); + log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), + ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue()); } } else { - String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", + String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); service.sentLogsToThingsboard(msg, registration); log.error("[{}] - [{}] [{}] error SendRequest", ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); - if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) { - this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate); - } } - }, e -> { - String msg = String.format(LOG_LW2M_ERROR + ": sendRequest: Resource path - %s msg error - %s SendRequest to Client", - request.getPath().toString(), e.toString()); + if (!lwM2MClient.isInit()) { + lwM2MClient.initValue(this.service, request.getPath().toString()); + } + String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client", + LOG_LW2M_ERROR, request.getPath().toString(), e.toString()); service.sentLogsToThingsboard(msg, registration); log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); - if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest() && isDelayedUpdate) { - this.handleResponseError(registration, request.getPath().toString(), lwM2MClient, isDelayedUpdate); - } }); + } private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type, Registration registration) { @@ -308,54 +297,27 @@ public class LwM2MTransportRequest { } } - private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { + private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request) { executorResponse.submit(() -> { try { - sendResponse(registration, path, response, request, lwM2MClient, isDelayedUpdate); + sendResponse(registration, path, response, request); } catch (Exception e) { log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", registration.getEndpoint(), path, e); } }); } - private void handleResponseError(Registration registration, final String path, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { - executorResponseError.submit(() -> { - try { - if (isDelayedUpdate) lwM2MClient.onSuccessOrErrorDelayedRequests(path); - } catch (RuntimeException t) { - log.error("[{}] endpoint [{}] path [{}] RuntimeException Unable to after send response.", registration.getEndpoint(), path, t); - } - }); - } - /** * processing a response from a client * @param registration - * @param path - * @param response - - * @param lwM2MClient - */ - private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request, LwM2MClient lwM2MClient, boolean isDelayedUpdate) { - if (response instanceof ObserveResponse) { + private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request) { + if (response instanceof ReadResponse) { service.onObservationResponse(registration, path, (ReadResponse) response); } else if (response instanceof CancelObservationResponse) { log.info("[{}] Path [{}] CancelObservationResponse 3_Send", path, response); - } else if (response instanceof ReadResponse) { - /** - * Use only at the first start after registration - * Fill with data -> Model client - */ - if (lwM2MClient != null) { - if (lwM2MClient.getPendingRequests().size() > 0) { - lwM2MClient.onSuccessHandler(path, response); - } - } - /** - * Use after registration on request - */ - else { - service.onObservationResponse(registration, path, (ReadResponse) response); - } } else if (response instanceof DeleteResponse) { log.info("[{}] Path [{}] DeleteResponse 5_Send", path, response); } else if (response instanceof DiscoverResponse) { @@ -366,7 +328,7 @@ public class LwM2MTransportRequest { log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", path, response); } else if (response instanceof WriteResponse) { log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", path, response); - service.onAttributeUpdateOk(registration, path, (WriteRequest) request, isDelayedUpdate); + service.onWriteResponseOk(registration, path, (WriteRequest) request); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java index 388138d8c3..5981a05e06 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerConfiguration.java @@ -19,37 +19,25 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; -import org.eclipse.leshan.core.node.codec.LwM2mNodeDecoder; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.VersionedModelProvider; -import org.eclipse.leshan.server.redis.RedisRegistrationStore; -import org.eclipse.leshan.server.redis.RedisSecurityStore; import org.eclipse.leshan.server.security.DefaultAuthorizer; -import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; +import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.util.Pool; import java.math.BigInteger; -import java.net.URI; -import java.net.URISyntaxException; import java.security.AlgorithmParameters; -import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.CertificateEncodingException; @@ -58,26 +46,26 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; import static org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler.getCoapConfig; - @Slf4j -@ComponentScan("org.thingsboard.server.transport.lwm2m.server") -@ComponentScan("org.thingsboard.server.transport.lwm2m.utils") -@Configuration("LwM2MTransportServerConfiguration") +@Component("LwM2MTransportServerConfiguration") @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerConfiguration { private PublicKey publicKey; private PrivateKey privateKey; + private boolean pskMode = false; @Autowired private LwM2MTransportContextServer context; @@ -85,35 +73,18 @@ public class LwM2MTransportServerConfiguration { @Autowired private LwM2mInMemorySecurityStore lwM2mInMemorySecurityStore; - @Primary - @Bean(name = "leshanServerPsk") - @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_psk:false}'=='true')") - public LeshanServer getLeshanServerPsk() { - log.info("Starting LwM2M transport ServerPsk... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPortNoSecPsk(), this.context.getCtxServer().getServerPortPsk(), PSK); - } - - @Bean(name = "leshanServerRpk") - @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_rpk:false}'=='true')") - public LeshanServer getLeshanServerRpk() { - log.info("Starting LwM2M transport ServerRpk... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPortNoSecRpk(), this.context.getCtxServer().getServerPortRpk(), RPK); - } - - @Bean(name = "leshanServerX509") - @ConditionalOnExpression("('${transport.lwm2m.server.secure.start_x509:false}'=='true')") - public LeshanServer getLeshanServerX509() { - log.info("Starting LwM2M transport ServerX509... PostConstruct"); - return getLeshanServer(this.context.getCtxServer().getServerPortNoSecX509(), this.context.getCtxServer().getServerPortX509(), X509); + @Bean + public LeshanServer getLeshanServer() { + log.info("Starting LwM2M transport Server... PostConstruct"); + return this.getLhServer(this.context.getCtxServer().getServerPortNoSec(), this.context.getCtxServer().getServerPortSecurity()); } - private LeshanServer getLeshanServer(Integer serverPortNoSec, Integer serverSecurePort, LwM2MSecurityMode dtlsMode) { + private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) { LeshanServerBuilder builder = new LeshanServerBuilder(); builder.setLocalAddress(this.context.getCtxServer().getServerHost(), serverPortNoSec); - builder.setLocalSecureAddress(this.context.getCtxServer().getServerSecureHost(), serverSecurePort); - builder.setEncoder(new DefaultLwM2mNodeEncoder()); - LwM2mNodeDecoder decoder = new DefaultLwM2mNodeDecoder(); - builder.setDecoder(decoder); + builder.setLocalSecureAddress(this.context.getCtxServer().getServerHostSecurity(), serverSecurePort); + builder.setDecoder(new DefaultLwM2mNodeDecoder()); + /** Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); /** Create CoAP Config */ @@ -123,181 +94,76 @@ public class LwM2MTransportServerConfiguration { LwM2mModelProvider modelProvider = new VersionedModelProvider(this.context.getCtxServer().getModelsValue()); builder.setObjectModelProvider(modelProvider); - /** Create DTLS security mode - * There can be only one DTLS security mode - */ - this.LwM2MSetSecurityStoreServer(builder, dtlsMode); + /** Create credentials */ + this.setServerWithCredentials(builder); + + /** Set securityStore with new registrationStore */ + builder.setSecurityStore(lwM2mInMemorySecurityStore); + /** Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - if (dtlsMode==PSK) { - dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isRecommendedCiphers()); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getCtxServer().isRecommendedSupportedGroups()); - dtlsConfig.setSupportedCipherSuites(TLS_PSK_WITH_AES_128_CBC_SHA256); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getCtxServer().isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getCtxServer().isRecommendedCiphers()); + if (this.pskMode) { + dtlsConfig.setSupportedCipherSuites( + TLS_PSK_WITH_AES_128_CCM_8, + TLS_PSK_WITH_AES_128_CBC_SHA256); } - else { - dtlsConfig.setRecommendedSupportedGroupsOnly(!this.context.getCtxServer().isRecommendedSupportedGroups()); - dtlsConfig.setRecommendedCipherSuitesOnly(!this.context.getCtxServer().isRecommendedCiphers()); + else { + dtlsConfig.setSupportedCipherSuites( + TLS_PSK_WITH_AES_128_CCM_8, + TLS_PSK_WITH_AES_128_CBC_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); } - /** Set DTLS Config */ - builder.setDtlsConfig(dtlsConfig); - /** Use a magic converter to support bad type send by the UI. */ - builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); + /** Set DTLS Config */ + builder.setDtlsConfig(dtlsConfig); /** Create LWM2M server */ return builder.build(); } - private void LwM2MSetSecurityStoreServer(LeshanServerBuilder builder, LwM2MSecurityMode dtlsMode) { - /** Set securityStore with new registrationStore */ - EditableSecurityStore securityStore = lwM2mInMemorySecurityStore; - switch (dtlsMode) { - /** Use PSK only */ - case PSK: - generatePSK_RPK(); - infoParamsPSK(); - break; - /** Use RPK only */ - case RPK: - generatePSK_RPK(); - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); - builder.setPrivateKey(this.privateKey); - infoParamsRPK(); - } - break; - /** Use x509 only */ - case X509: - setServerWithX509Cert(builder); - break; - /** No security */ - case NO_SEC: - builder.setTrustedCertificates(new X509Certificate[0]); - break; - /** Use x509 with EST */ - case X509_EST: - // TODO support sentinel pool and make pool configurable - break; - case REDIS: - /** - * Set securityStore with new registrationStore (if use redis store) - * Connect to redis - */ - Pool jedis = null; - try { - jedis = new JedisPool(new URI(this.context.getCtxServer().getRedisUrl())); - securityStore = new RedisSecurityStore(jedis); - builder.setRegistrationStore(new RedisRegistrationStore(jedis)); - } catch (URISyntaxException e) { - log.error("", e); - } - break; - default: - } - - /** Set securityStore with registrationStore (if x509)*/ - if (dtlsMode == X509) { - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { - @Override - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, - String expectedX509CommonName) { - return endpoint.startsWith(expectedX509CommonName); - } - })); - } - - /** Set securityStore with new registrationStore */ - builder.setSecurityStore(securityStore); - } - - private void generatePSK_RPK() { - try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.context.getCtxServer().getServerPublicX() != null && !this.context.getCtxServer().getServerPublicX().isEmpty() && this.context.getCtxServer().getServerPublicY() != null && !this.context.getCtxServer().getServerPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - if (this.context.getCtxServer().getServerPrivateS() != null && !this.context.getCtxServer().getServerPrivateS().isEmpty()) { - /** Get point values */ - byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateS().toCharArray()); - /** Create key specs */ - KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ - this.privateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); - } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server PSK/RPK", e.getMessage()); - throw new RuntimeException(e); - } - } - - private void infoParamsPSK() { - log.info("\nServer uses PSK -> private key : \n security key : [{}] \n serverSecureURI : [{}]", - Hex.encodeHexString(this.privateKey.getEncoded()), - this.context.getCtxServer().getServerSecureHost() + ":" + Integer.toString(this.context.getCtxServer().getServerPortPsk())); - } - - private void infoParamsRPK() { - if (this.publicKey instanceof ECPublicKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) this.publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) this.publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) this.publicKey).getParams().toString(); - log.info( - " \nServer uses RPK : \n Elliptic Curve parameters : [{}] \n Public x coord : [{}] \n Public y coord : [{}] \n Public Key (Hex): [{}] \n Private Key (Hex): [{}]", - params, Hex.encodeHexString(x), Hex.encodeHexString(y), - Hex.encodeHexString(this.publicKey.getEncoded()), - Hex.encodeHexString(this.privateKey.getEncoded())); - } else { - throw new IllegalStateException("Unsupported Public Key Format (only ECPublicKey supported)."); - } - } - - - private void setServerWithX509Cert(LeshanServerBuilder builder) { + private void setServerWithCredentials(LeshanServerBuilder builder) { try { if (this.context.getCtxServer().getKeyStoreValue() != null) { - setBuilderX509(builder); - X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /** by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); + if (this.setBuilderX509(builder)) { + X509Certificate rootCAX509Cert = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getRootAlias()); + if (rootCAX509Cert != null) { + X509Certificate[] trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + builder.setTrustedCertificates(trustedCertificates); + } else { + /** by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + } + /** Set securityStore with registrationStore*/ + builder.setAuthorizer(new DefaultAuthorizer(lwM2mInMemorySecurityStore, new SecurityChecker() { + @Override + protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, + String expectedX509CommonName) { + return endpoint.startsWith(expectedX509CommonName); + } + })); } + } else if (this.setServerRPK(builder)) { + this.infoPramsUri("RPK"); + this.infoParamsServerKey(this.publicKey, this.privateKey); } else { /** by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); - log.error("Unable to load X509 files for LWM2MServer"); + log.info("Unable to load X509 files for LWM2MServer"); + this.pskMode = true; + this.infoPramsUri("PSK"); } } catch (KeyStoreException ex) { log.error("[{}] Unable to load X509 files server", ex.getMessage()); } } - private void setBuilderX509(LeshanServerBuilder builder) { + private boolean setBuilderX509(LeshanServerBuilder builder) { /** * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks @@ -305,38 +171,120 @@ public class LwM2MTransportServerConfiguration { try { X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerAlias()); PrivateKey privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); - builder.setPrivateKey(privateKey); - builder.setCertificateChain(new X509Certificate[]{serverCertificate}); - this.infoParamsX509(serverCertificate, privateKey); + PublicKey publicKey = serverCertificate.getPublicKey(); + if (serverCertificate != null && + privateKey != null && privateKey.getEncoded().length > 0 && + publicKey != null && publicKey.getEncoded().length > 0) { + builder.setPublicKey(serverCertificate.getPublicKey()); + builder.setPrivateKey(privateKey); + builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + this.infoParamsServerX509(serverCertificate, publicKey, privateKey); + return true; + } else { + return false; + } } catch (Exception ex) { log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); + return false; } -// /** -// * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE -// * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks -// */ -// try { -// X509Certificate serverCertificate = (X509Certificate) this.context.getCtxServer().getKeyStoreValue().getCertificate(this.context.getCtxServer().getServerPrivateS()); -// this.privateKey = (PrivateKey) this.context.getCtxServer().getKeyStoreValue().getKey(this.context.getCtxServer().getServerAlias(), this.context.getCtxServer().getKeyStorePasswordServer() == null ? null : this.context.getCtxServer().getKeyStorePasswordServer().toCharArray()); -// if (this.privateKey != null && this.privateKey.getEncoded().length > 0) { -// builder.setPrivateKey(this.privateKey); -// } -// if (serverCertificate != null) { -// builder.setCertificateChain(new X509Certificate[]{serverCertificate}); -// this.infoParamsX509(serverCertificate); -// } -// } catch (Exception ex) { -// log.error("[{}] Unable to load KeyStore files server", ex.getMessage()); -// } } - private void infoParamsX509(X509Certificate certificate, PrivateKey privateKey) { + private void infoParamsServerX509(X509Certificate certificate, PublicKey publicKey, PrivateKey privateKey) { try { - log.info("Server uses X509 : \n X509 Certificate (Hex): [{}] \n Private Key (Hex): [{}]", - Hex.encodeHexString(certificate.getEncoded()), - Hex.encodeHexString(privateKey.getEncoded())); + infoPramsUri("X509"); + log.info("\n- X509 Certificate (Hex): [{}]", + Hex.encodeHexString(certificate.getEncoded())); + this.infoParamsServerKey(publicKey, privateKey); } catch (CertificateEncodingException e) { log.error("", e); } } + + private void infoPramsUri(String mode) { + log.info("Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", + mode, + this.context.getCtxServer().getServerHost() + ":" + this.context.getCtxServer().getServerPortNoSec(), + this.context.getCtxServer().getServerHostSecurity() + ":" + this.context.getCtxServer().getServerPortSecurity()); + } + + private boolean setServerRPK(LeshanServerBuilder builder) { + try { + this.generateKeyForRPK(); + if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && + this.privateKey != null && this.privateKey.getEncoded().length > 0) { + builder.setPublicKey(this.publicKey); + builder.setPrivateKey(this.privateKey); + return true; + } + } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { + log.error("Fail create Server with RPK", e); + } + return false; + } + + + /** + * From yml: server + * public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" + * public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}" + * private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}" + */ + private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + /** Get Elliptic Curve Parameter spec for secp256r1 */ + AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); + algoParameters.init(new ECGenParameterSpec("secp256r1")); + ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); + if (this.context.getCtxServer().getServerPublicX() != null && + !this.context.getCtxServer().getServerPublicX().isEmpty() && + this.context.getCtxServer().getServerPublicY() != null && + !this.context.getCtxServer().getServerPublicY().isEmpty()) { + /** Get point values */ + byte[] publicX = Hex.decodeHex(this.context.getCtxServer().getServerPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(this.context.getCtxServer().getServerPublicY().toCharArray()); + /** Create key specs */ + KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), + parameterSpec); + /** Get keys */ + this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); + } + if (this.context.getCtxServer().getServerPrivateEncoded() != null && + !this.context.getCtxServer().getServerPrivateEncoded().isEmpty()) { + /** Get private key */ + byte[] privateS = Hex.decodeHex(this.context.getCtxServer().getServerPrivateEncoded().toCharArray()); + try { + this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); + } catch (InvalidKeySpecException ignore2) { + log.error("Invalid Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.context.getCtxServer().getServerPrivateEncoded()); + } + } + } + + private void infoParamsServerKey(PublicKey publicKey, PrivateKey privateKey) { + /** Get x coordinate */ + byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); + if (x[0] == 0) + x = Arrays.copyOfRange(x, 1, x.length); + + /** Get Y coordinate */ + byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); + if (y[0] == 0) + y = Arrays.copyOfRange(y, 1, y.length); + + /** Get Curves params */ + String params = ((ECPublicKey) publicKey).getParams().toString(); + String privHex = Hex.encodeHexString(privateKey.getEncoded()); + log.info(" \n- Public Key (Hex): [{}] \n" + + "- Private Key (Hex): [{}], \n" + + "public_x: \"${LWM2M_SERVER_PUBLIC_X:{}}\" \n" + + "public_y: \"${LWM2M_SERVER_PUBLIC_Y:{}}\" \n" + + "private_encoded: \"${LWM2M_SERVER_PRIVATE_ENCODED:{}}\" \n" + + "- Elliptic Curve parameters : [{}]", + Hex.encodeHexString(publicKey.getEncoded()), + privHex, + Hex.encodeHexString(x), + Hex.encodeHexString(y), + privHex, + params); + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java index 90215326b4..2805fd7f91 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServerInitializer.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.californium.LeshanServer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; @@ -31,69 +30,35 @@ import javax.annotation.PreDestroy; @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' ) || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled}'=='true')") public class LwM2MTransportServerInitializer { - @Autowired private LwM2MTransportServiceImpl service; @Autowired(required = false) - @Qualifier("leshanServerX509") - private LeshanServer lhServerX509; - - @Autowired(required = false) - @Qualifier("leshanServerPsk") - private LeshanServer lhServerPsk; - - @Autowired(required = false) - @Qualifier("leshanServerRpk") - private LeshanServer lhServerRpk; + private LeshanServer leshanServer; @Autowired private LwM2MTransportContextServer context; @PostConstruct public void init() { - if (this.context.getCtxServer().getEnableGenPskRpk()) new LWM2MGenerationPSkRPkECC(); - if (this.context.getCtxServer().isServerStartPsk()) { - this.startLhServerPsk(); - } - if (this.context.getCtxServer().isServerStartRpk()) { - this.startLhServerRpk(); + if (this.context.getCtxServer().getEnableGenNewKeyPskRpk()) { + new LWM2MGenerationPSkRPkECC(); } - if (this.context.getCtxServer().isServerStartX509()) { - this.startLhServerX509(); - } - } - - private void startLhServerPsk() { - this.lhServerPsk.start(); - LwM2mServerListener lhServerPskListener = new LwM2mServerListener(this.lhServerPsk, service); - this.lhServerPsk.getRegistrationService().addListener(lhServerPskListener.registrationListener); - this.lhServerPsk.getPresenceService().addListener(lhServerPskListener.presenceListener); - this.lhServerPsk.getObservationService().addListener(lhServerPskListener.observationListener); - } - - private void startLhServerRpk() { - this.lhServerRpk.start(); - LwM2mServerListener lhServerRpkListener = new LwM2mServerListener(this.lhServerRpk, service); - this.lhServerRpk.getRegistrationService().addListener(lhServerRpkListener.registrationListener); - this.lhServerRpk.getPresenceService().addListener(lhServerRpkListener.presenceListener); - this.lhServerRpk.getObservationService().addListener(lhServerRpkListener.observationListener); + this.startLhServer(); } - private void startLhServerX509() { - this.lhServerX509.start(); - LwM2mServerListener lhServerCertListener = new LwM2mServerListener(this.lhServerX509, service); - this.lhServerX509.getRegistrationService().addListener(lhServerCertListener.registrationListener); - this.lhServerX509.getPresenceService().addListener(lhServerCertListener.presenceListener); - this.lhServerX509.getObservationService().addListener(lhServerCertListener.observationListener); + private void startLhServer() { + this.leshanServer.start(); + LwM2mServerListener lhServerCertListener = new LwM2mServerListener(this.leshanServer, service); + this.leshanServer.getRegistrationService().addListener(lhServerCertListener.registrationListener); + this.leshanServer.getPresenceService().addListener(lhServerCertListener.presenceListener); + this.leshanServer.getObservationService().addListener(lhServerCertListener.observationListener); } @PreDestroy public void shutdown() { log.info("Stopping LwM2M transport Server!"); - lhServerPsk.destroy(); - lhServerRpk.destroy(); - lhServerX509.destroy(); + leshanServer.destroy(); log.info("LwM2M transport Server stopped!"); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java index e412e113e9..31e2fc7671 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportServiceImpl.java @@ -15,18 +15,19 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; @@ -44,18 +45,18 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientProfile; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; import org.thingsboard.server.transport.lwm2m.server.secure.LwM2mInMemorySecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -72,7 +73,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -149,12 +149,10 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { executorRegistered.submit(() -> { try { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.updateInSessionsLwM2MClient(lwServer, registration); + LwM2MClient lwM2MClient = this.lwM2mInMemorySecurityStore.updateInSessionsLwM2MClient(lwServer, registration); if (lwM2MClient != null) { lwM2MClient.setLwM2MTransportServiceImpl(this); - lwM2MClient.setSessionUuid(UUID.randomUUID()); this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client Registered", registration); - this.setLwM2mFromClientValue(lwServer, registration, lwM2MClient); SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); if (sessionInfo != null) { lwM2MClient.setDeviceUuid(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -165,7 +163,8 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); this.sentLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); - this.putDelayedUpdateResourcesThingsboard(lwM2MClient); + this.initLwM2mFromClientValue(lwServer, registration, lwM2MClient); + } else { log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); } @@ -237,6 +236,151 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { //TODO: associate endpointId with device information. } + @Override + public void setCancelObservations(LeshanServer lwServer, Registration registration) { + if (registration != null) { + Set observations = lwServer.getObservationService().getObservations(registration); + observations.forEach(observation -> this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString())); + } + } + + /** + * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); + * At server side this will not remove the observation from the observation store, to do it you need to use + * {@code ObservationService#cancelObservation()} + */ + @Override + public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { + lwServer.getObservationService().cancelObservations(registration, path); + } + + /** + * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource + * + * @param registration - Registration LwM2M Client + * @param path - observe + * @param response - observe + */ + @Override + public void onObservationResponse(Registration registration, String path, ReadResponse response) { + if (response.getContent() != null) { + if (response.getContent() instanceof LwM2mObject) { + LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); + this.updateObjectResourceValue(registration, lwM2mObject, path); + } else if (response.getContent() instanceof LwM2mObjectInstance) { + LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); + this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path); + } else if (response.getContent() instanceof LwM2mResource) { + LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); + this.updateResourcesValue(registration, lwM2mResource, path); + } + } + } + + /** + * Update - sent request in change value resources in Client + * Path to resources from profile equal keyName or from ModelObject equal name + * Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) + * Delete - nothing * + * + * @param msg - + */ + @Override + public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { + if (msg.getSharedUpdatedCount() > 0) { + JsonElement el = JsonConverter.toJson(msg); + el.getAsJsonObject().entrySet().forEach(de -> { + String path = this.getPathAttributeUpdate(sessionInfo, de.getKey()); + String value = de.getValue().getAsString(); + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); + LwM2MClientProfile profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + ResourceModel resourceModel = context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path)); + if (!path.isEmpty() && (this.validatePathInAttrProfile(profile, path) || this.validatePathInTelemetryProfile(profile, path))) { + if (resourceModel != null && resourceModel.operations.isWritable()) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), null, value, this.context.getCtxServer().getTimeout()); + } else { + log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); + String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", + LOG_LW2M_ERROR, path, value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + } else { + log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); + String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", + LOG_LW2M_ERROR, de.getKey(), value); + this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + } + }); + } else if (msg.getSharedDeletedCount() > 0) { + log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); + } + } + + /** + * @param sessionInfo - + * @param deviceProfile - + */ + @Override + public void onDeviceProfileUpdate(SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { + Set registrationIds = lwM2mInMemorySecurityStore.getSessions().entrySet() + .stream() + .filter(e -> e.getValue().getProfileUuid().equals(deviceProfile.getUuidId())) + .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); + if (registrationIds.size() > 0) { + this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); + } + } + + /** + * @param sessionInfo - + * @param device - + * @param deviceProfileOpt - + */ + @Override + public void onDeviceUpdate(SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() + .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) + .map(Map.Entry::getKey) + .findFirst(); + registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); + } + + /** + * Trigger Server path = "/1/0/8" + * + * Trigger bootStrap path = "/1/0/9" - have to implemented on client + */ + @Override + public void doTrigger(LeshanServer lwServer, Registration registration, String path) { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, + ContentFormat.TLV.getName(), null, null, this.context.getCtxServer().getTimeout()); + } + + /** + * Deregister session in transport + * + * @param sessionInfo - lwm2m client + */ + @Override + public void doDisconnect(SessionInfoProto sessionInfo) { + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + } + + /** + * Session device in thingsboard is closed + * + * @param sessionInfo - lwm2m client + */ + private void doCloseSession(SessionInfoProto sessionInfo) { + TransportProtos.SessionEvent event = SessionEvent.CLOSED; + TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) + .setEvent(event).build(); + transportService.process(sessionInfo, msg, null); + } + /** * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, * * if you need to do long time processing use a dedicated thread pool. @@ -253,7 +397,7 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { * Removes a profile if not used in sessions */ private void syncSessionsAndProfiles() { - Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() + Map profilesClone = lwM2mInMemorySecurityStore.getProfiles().entrySet() .stream() .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); profilesClone.forEach((k, v) -> { @@ -270,257 +414,261 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { } /** - * #0 Add new ObjectModel to context - * Create new LwM2MClient for current session -> setModelClient... - * #1 Add all ObjectLinks (instance) to control the process of executing requests to the client - * to get the client model with current values - * #2 Get the client model with current values. Analyze the response in -> lwM2MTransportRequest.sendResponse + * @param msg - text msg + * @param registration - Id of Registration LwM2M Client + */ + public void sentLogsToThingsboard(String msg, Registration registration) { + if (msg != null) { + JsonObject telemetries = new JsonObject(); + telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); + this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); + } + } + + + /** + * // !!! Ok + * Prepare Sent to Thigsboard callback - Attribute or Telemetry + * + * @param msg - JsonArray: [{name: value}] + * @param topicName - Api Attribute or Telemetry + * @param registration - Id of Registration LwM2M Client + */ + public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (sessionInfo != null) { + context.sentParametersOnThingsboard(msg, topicName, sessionInfo); + } else { + log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); + } + } + + /** + * #1 сlientOnlyObserveAfterConnect == true + * - Only Observe Request to the client marked as observe from the profile configuration. + * #2. сlientOnlyObserveAfterConnect == false & clientUpdateValueAfterConnect == false + * - Request to the client after registration to read the values of the resources marked as attribute or telemetry from the profile configuration. + * - then Observe Request to the client marked as observe from the profile configuration. + * #3. сlientOnlyObserveAfterConnect == false & clientUpdateValueAfterConnect == true + * После регистрации отправляю запрос на read всех ресурсов, котрые послк регистрации, а затем запрос на observe (edited) + * - Request to the client after registration to read all resource values for all objects + * - then Observe Request to the client marked as observe from the profile configuration. * * @param lwServer - LeshanServer * @param registration - Registration LwM2M Client * @param lwM2MClient - object with All parameters off client */ - private void setLwM2mFromClientValue(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - // #1 - lwM2MClient.getPendingRequests().add(url.getUrl()); - // #2 - lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); - } - }); + private void initLwM2mFromClientValue(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient) { + LwM2MClientProfile lwM2MClientProfile = lwM2mInMemorySecurityStore.getProfile(registration.getId()); + Set clientObjects = this.getAllOjectsInClient(registration); + if (clientObjects != null && !LwM2MTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { + // #2 + if (!LwM2MTransportHandler.getClientUpdateValueAfterConnect(lwM2MClientProfile)) { + this.initReadAttrTelemetryObserveToClient(lwServer, registration, lwM2MClient, GET_TYPE_OPER_READ); - // #1 - for (Link url : registration.getObjectLinks()) { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MClient.getPendingRequests().add(url.getUrl()); } - } - // #2 - for (Link url : registration.getObjectLinks()) { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); + // #3 + else { + lwM2MClient.getPendingRequests().addAll(clientObjects); + clientObjects.forEach(path -> { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), + null, null, this.context.getCtxServer().getTimeout()); + }); } } - // #1 - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MClient.getPendingRequests().add(url.getUrl()); - } + this.initReadAttrTelemetryObserveToClient(lwServer, registration, lwM2MClient, GET_TYPE_OPER_OBSERVE); + } + + /** + * @param registration - + * @param lwM2mObject - + * @param path - + */ + private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String path) { + LwM2mPath pathIds = new LwM2mPath(path); + lwM2mObject.getInstances().forEach((instanceId, instance) -> { + String pathInstance = pathIds.toString() + "/" + instanceId; + this.updateObjectInstanceResourceValue(registration, instance, pathInstance); }); + } - // #2 - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance() && !pathIds.isResource()) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, url.getUrl(), GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - lwM2MClient, null, null, this.context.getCtxServer().getTimeout(), false); - } + /** + * @param registration - + * @param lwM2mObjectInstance - + * @param path - + */ + private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String path) { + LwM2mPath pathIds = new LwM2mPath(path); + lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { + String pathRez = pathIds.toString() + "/" + resourceId; + this.updateResourcesValue(registration, resource, pathRez); }); } /** + * Sending observe value of resources to thingsboard + * #1 Return old Value Resource from LwM2MClient + * #2 Update new Resources (replace old Resource Value on new Resource Value) + * * @param registration - Registration LwM2M Client - * @return - sessionInfo after access connect client + * @param - LwM2mSingleResource response.getContent() + * @param - LwM2mSingleResource response.getContent() + * @param path - resource */ - private SessionInfoProto getValidateSessionInfo(Registration registration) { + private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); - return getNewSessionInfoProto(lwM2MClient); - + lwM2MClient.updateResourceValue(path, lwM2mResource); + Set paths = new HashSet<>(); + paths.add(path); + this.updateAttrTelemetry(registration, false, paths); } /** - * @param registrationId - - * @return - + * Sent Attribute and Telemetry to Thingsboard + * #1 - get AttrName/TelemetryName with value: + * #1.1 from Client + * #1.2 from LwM2MClient: + * -- resourceId == path from LwM2MClientProfile.postAttributeProfile/postTelemetryProfile/postObserveProfile + * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) + * #2 - set Attribute/Telemetry + * + * @param registration - Registration LwM2M Client */ - private SessionInfoProto getValidateSessionInfo(String registrationId) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); - return getNewSessionInfoProto(lwM2MClient); - } - - private SessionInfoProto getNewSessionInfoProto(LwM2MClient lwM2MClient) { - if (lwM2MClient != null) { - ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); - if (msg == null || msg.getDeviceInfo() == null) { - log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); - this.closeClientSession(lwM2MClient.getRegistration()); - return null; - } else { - return SessionInfoProto.newBuilder() - .setNodeId(this.context.getNodeId()) - .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) - .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setDeviceName(msg.getDeviceInfo().getDeviceName()) - .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) - .build(); + private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { + JsonObject attributes = new JsonObject(); + JsonObject telemetries = new JsonObject(); + if (start) { + // #1.1 + JsonObject attributeClient = this.getAttributeClient(registration); + if (attributeClient != null) { + attributeClient.entrySet().forEach(p -> attributes.add(p.getKey(), p.getValue())); } } - return null; + // #1.2 + try { + writeLock.lock(); + this.getParametersFromProfile(attributes, telemetries, registration, paths); + } catch (Exception e) { + log.error("UpdateAttrTelemetry", e); + } finally { + writeLock.unlock(); + } + if (attributes.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); + if (telemetries.getAsJsonObject().entrySet().size() > 0) + this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); } /** - * Add attribute/telemetry information from Client and credentials/Profile to client model and start observe - * !!! if the resource has an observation, but no telemetry or attribute - the observation will not use - * #1 Sending Attribute Telemetry with value to thingsboard only once at the start of the connection - * #2 Start observe - * - * @param lwM2MClient - LwM2M Client + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile */ - - public void updatesAndSentModelParameter(LwM2MClient lwM2MClient) { - // #1 - this.updateAttrTelemetry(lwM2MClient.getRegistration(), true, null); - // #2 - this.onSentObserveToClient(lwM2MClient.getLwServer(), lwM2MClient.getRegistration()); - + private boolean validatePathInAttrProfile(LwM2MClientProfile profile, String path) { + Set attributesSet = new Gson().fromJson(profile.getPostAttributeProfile(), new TypeToken<>(){}.getType()); + return attributesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); } /** - * If there is a difference in values between the current resource values and the shared attribute values - * when the client connects to the server - * #1 get attributes name from profile include name resources in ModelObject if resource isWritable - * #2.1 #1 size > 0 => send Request getAttributes to thingsboard - * #2.2 #1 size == 0 => continue normal process - * - * @param lwM2MClient - LwM2M Client + * @param profile - + * @param path - + * @return true if path isPresent in postAttributeProfile */ - public void putDelayedUpdateResourcesThingsboard(LwM2MClient lwM2MClient) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); - if (sessionInfo != null) { - //#1.1 + #1.2 - List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); - if (attrSharedNames.size() > 0) { - //#2.1 - try { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAdaptor().convertToGetAttributes(null, attrSharedNames); - lwM2MClient.getDelayedRequestsId().add(getAttributeMsg.getRequestId()); - transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); - } catch (AdaptorException e) { - log.warn("Failed to decode get attributes request", e); - } - } - // #2.2 - else { - lwM2MClient.onSuccessOrErrorDelayedRequests(null); - } - } + private boolean validatePathInTelemetryProfile(LwM2MClientProfile profile, String path) { + Set telemetriesSet = new Gson().fromJson(profile.getPostTelemetryProfile(), new TypeToken<>(){}.getType()); + return telemetriesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); } /** - * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values - * #1 Get path resource by result attributesResponse - * #1.1 If two names have equal path => last time attribute - * #2.1 if there is a difference in values between the current resource values and the shared attribute values - * => sent to client Request Update of value (new value from shared attribute) - * and LwM2MClient.delayedRequests.add(path) - * #2.1 if there is not a difference in values between the current resource values and the shared attribute values + * Start observe/read: Attr/Telemetry + * #1 - Analyze: + * #1.1 path in resource profile == client resource * - * @param attributesResponse - - * @param sessionInfo - + * @param lwServer - + * @param registration - */ - public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(sessionInfo); - if (lwM2MClient.getDelayedRequestsId().contains(attributesResponse.getRequestId())) { - attributesResponse.getSharedAttributeListList().forEach(attr -> { - String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); + private void initReadAttrTelemetryObserveToClient(LeshanServer lwServer, Registration registration, LwM2MClient lwM2MClient, String typeOper) { + try { + LwM2MClientProfile lwM2MClientProfile = lwM2mInMemorySecurityStore.getProfile(registration.getId()); + Set clientInstances = this.getAllInstancesInClient(registration); + Set result; + if (GET_TYPE_OPER_READ.equals(typeOper)) { + result = new ObjectMapper().readValue(lwM2MClientProfile.getPostAttributeProfile().getAsJsonArray().toString().getBytes(), new TypeReference<>() {}); + result.addAll(new ObjectMapper().readValue(lwM2MClientProfile.getPostTelemetryProfile().getAsJsonArray().toString().getBytes(), new TypeReference<>() {})); + } else { + result = new ObjectMapper().readValue(lwM2MClientProfile.getPostObserveProfile().getAsJsonArray().toString().getBytes(), new TypeReference<>() {}); + } + Set pathSent = ConcurrentHashMap.newKeySet(); + result.forEach(p -> { // #1.1 - if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { - lwM2MClient.getDelayedRequests().put(path, attr); - } else { - lwM2MClient.getDelayedRequests().put(path, attr); + String target = p; + String[] resPath = target.split("/"); + String instance = "/" + resPath[1] + "/" + resPath[2]; + if (clientInstances.contains(instance)) { + pathSent.add(target); } }); - // #2.1 - lwM2MClient.getDelayedRequests().forEach((k, v) -> { - ArrayList listV = new ArrayList<>(); - listV.add(v.getKv()); - this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); + lwM2MClient.getPendingRequests().addAll(pathSent); + pathSent.forEach(target -> { + lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, ContentFormat.TLV.getName(), + null, null, this.context.getCtxServer().getTimeout()); }); - lwM2MClient.getDelayedRequestsId().remove(attributesResponse.getRequestId()); - if (lwM2MClient.getDelayedRequests().size() == 0) { - lwM2MClient.onSuccessOrErrorDelayedRequests(null); + if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + lwM2MClient.initValue(this, null); } - } - } - - private void putDelayedUpdateResourcesClient(LwM2MClient lwM2MClient, Object valueOld, Object valueNew, String path) { - if (valueNew != null && !valueNew.toString().equals(valueOld.toString())) { - lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), lwM2MClient, null, valueNew, this.context.getCtxServer().getTimeout(), - true); + } catch (IOException e) { + e.printStackTrace(); } } /** - * Get names and keyNames from profile shared!!!! attr resources IsWritable + * Update parameters device in LwM2MClient + * If new deviceProfile != old deviceProfile => update deviceProfile * - * @param lwM2MClient - - * @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable + * @param registrationId - + * @param device - */ - private List getNamesAttrFromProfileIsWritable(LwM2MClient lwM2MClient) { - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(lwM2MClient.getProfileUuid()); - Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); - ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), ConcurrentHashMap.class); - - ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() - .stream() - .filter(e -> (attrSet.contains(e.getKey()) && context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null && - context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable())) - .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); + private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); + lwM2MClient.setDeviceName(device.getName()); + if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { + Set registrationIds = new HashSet<>(); + registrationIds.add(registrationId); + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); + } - Set namesIsWritable = ConcurrentHashMap.newKeySet(); - namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); - return new ArrayList<>(namesIsWritable); + lwM2MClient.setProfileUuid(device.getDeviceProfileId().getId()); } + /** + * @param registration - + * @return - all object in client + */ + private Set getAllOjectsInClient(Registration registration) { + Set clientObjects = ConcurrentHashMap.newKeySet(); + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance()) { + clientObjects.add("/" + pathIds.getObjectId()); + } + }); + return (clientObjects.size() > 0) ? clientObjects : null; + } /** - * Sent Attribute and Telemetry to Thingsboard - * #1 - get AttrName/TelemetryName with value: - * #1.1 from Client - * #1.2 from LwM2MClient: - * -- resourceId == path from AttrTelemetryObserveValue.postAttributeProfile/postTelemetryProfile/postObserveProfile - * -- AttrName/TelemetryName == resourceName from ModelObject.objectModel, value from ModelObject.instance.resource(resourceId) - * #2 - set Attribute/Telemetry - * - * @param registration - Registration LwM2M Client + * @param registration - + * @return all instances in client */ - private void updateAttrTelemetry(Registration registration, boolean start, Set paths) { - JsonObject attributes = new JsonObject(); - JsonObject telemetries = new JsonObject(); - if (start) { - // #1.1 - JsonObject attributeClient = this.getAttributeClient(registration); - if (attributeClient != null) { - attributeClient.entrySet().forEach(p -> attributes.add(p.getKey(), p.getValue())); + private Set getAllInstancesInClient(Registration registration) { + Set clientInstances = ConcurrentHashMap.newKeySet(); + Arrays.stream(registration.getObjectLinks()).forEach(url -> { + LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + if (pathIds.isObjectInstance()) { + clientInstances.add(url.getUrl()); } - } - // #1.2 - try { - writeLock.lock(); - this.getParametersFromProfile(attributes, telemetries, registration, paths); - } catch (Exception e) { - log.error("UpdateAttrTelemetry", e); - } finally { - writeLock.unlock(); - } - if (attributes.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); - if (telemetries.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); + }); + return (clientInstances.size() > 0) ? clientInstances : null; } /** @@ -542,13 +690,11 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { * @param attributes - new JsonObject * @param telemetry - new JsonObject * @param registration - Registration LwM2M Client - * result: add to JsonObject those resources to which the user is subscribed and they have a value - * if path==null add All resources else only one - * (attributes/telemetry): new {name(Attr/Telemetry):value} + * @param path */ private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid()); - attrTelemetryObserveValue.getPostAttributeProfile().forEach(p -> { + LwM2MClientProfile lwM2MClientProfile = lwM2mInMemorySecurityStore.getProfile(registration.getId()); + lwM2MClientProfile.getPostAttributeProfile().forEach(p -> { LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); if (pathIds.isResource()) { if (path == null || path.contains(p.getAsString())) { @@ -556,7 +702,7 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { } } }); - attrTelemetryObserveValue.getPostTelemetryProfile().forEach(p -> { + lwM2MClientProfile.getPostTelemetryProfile().forEach(p -> { LwM2mPath pathIds = new LwM2mPath(p.getAsString().toString()); if (pathIds.isResource()) { if (path == null || path.contains(p.getAsString())) { @@ -567,338 +713,59 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { } /** - * @param parameters - JsonObject attributes/telemetry - * @param registration - Registration LwM2M Client - */ - private void addParameters(String path, JsonObject parameters, Registration registration) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()); - JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()).getPostKeyNameProfile(); - String resName = String.valueOf(names.get(path)); - if (resName != null && !resName.isEmpty()) { - try { - String resValue = this.getResourceValueToString(lwM2MClient, path); - if (resValue != null) { - parameters.addProperty(resName, resValue); - } - } catch (Exception e) { - log.error(e.getStackTrace().toString()); - } - } - } - - /** - * Prepare Sent to Thigsboard callback - Attribute or Telemetry - * - * @param msg - JsonArray: [{name: value}] - * @param topicName - Api Attribute or Telemetry - * @param registration - Id of Registration LwM2M Client - */ - public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - context.sentParametersOnThingsboard(msg, topicName, sessionInfo); - } else { - log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); - } - } - - /** - * Start observe - * #1 - Analyze: - * #1.1 path in observe == (attribute or telemetry) - * #2 Analyze after sent request (response): - * #2.1 First: lwM2MTransportRequest.sendResponse -> ObservationListener.newObservation - * #2.2 Next: ObservationListener.onResponse * - * - * @param lwServer - LeshanServer - * @param registration - Registration LwM2M Client - */ - private void onSentObserveToClient(LeshanServer lwServer, Registration registration) { - if (lwServer.getObservationService().getObservations(registration).size() > 0) { - this.setCancelObservations(lwServer, registration); - } - UUID profileUUid = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()).getProfileUuid(); - AttrTelemetryObserveValue attrTelemetryObserveValue = lwM2mInMemorySecurityStore.getProfiles().get(profileUUid); - attrTelemetryObserveValue.getPostObserveProfile().forEach(p -> { - // #1.1 - String target = (getValidateObserve(attrTelemetryObserveValue.getPostAttributeProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : (getValidateObserve(attrTelemetryObserveValue.getPostTelemetryProfile(), p.getAsString().toString())) ? - p.getAsString().toString() : null; - if (target != null) { - // #2 - if (this.getResourceValueToString(lwM2mInMemorySecurityStore.getSessions().get(registration.getId()), target) != null) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, GET_TYPE_OPER_OBSERVE, - null, null, null, null, this.context.getCtxServer().getTimeout(), - false); - } - } - }); - } - - public void setCancelObservations(LeshanServer lwServer, Registration registration) { - if (registration != null) { - Set observations = lwServer.getObservationService().getObservations(registration); - observations.forEach(observation -> this.setCancelObservationRecourse(lwServer, registration, observation.getPath().toString())); - } - } - - /** - * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - * At server side this will not remove the observation from the observation store, to do it you need to use - * {@code ObservationService#cancelObservation()} - */ - public void setCancelObservationRecourse(LeshanServer lwServer, Registration registration, String path) { - lwServer.getObservationService().cancelObservations(registration, path); - } - - /** - * @param parameters - JsonArray postAttributeProfile/postTelemetryProfile - * @param path - recourse from postObserveProfile - * @return rez - true if path observe is in attribute/telemetry - */ - private boolean getValidateObserve(JsonElement parameters, String path) { - AtomicBoolean rez = new AtomicBoolean(false); - if (parameters.isJsonArray()) { - parameters.getAsJsonArray().forEach(p -> { - if (p.getAsString().toString().equals(path)) rez.set(true); - } - ); - } else if (parameters.isJsonObject()) { - rez.set((parameters.getAsJsonObject().entrySet()).stream().map(json -> json.toString()) - .filter(path::equals).findAny().orElse(null) != null); - } - return rez.get(); - } - - /** - * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource - * - * @param registration - Registration LwM2M Client - * @param path - observe - * @param response - observe - */ - - public void onObservationResponse(Registration registration, String path, ReadResponse response) { - if (response.getContent() != null) { - if (response.getContent() instanceof LwM2mObject) { -// LwM2mObject content = (LwM2mObject) response.getContent(); - } else if (response.getContent() instanceof LwM2mObjectInstance) { -// LwM2mObjectInstance content = (LwM2mObjectInstance) response.getContent(); - } else if (response.getContent() instanceof LwM2mSingleResource) { - LwM2mSingleResource content = (LwM2mSingleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, content.getValue(), null, path); - } else if (response.getContent() instanceof LwM2mMultipleResource) { - LwM2mMultipleResource content = (LwM2mMultipleResource) response.getContent(); - this.onObservationSetResourcesValue(registration, null, content.getValues(), path); - } - } - } - - /** - * Sending observe value of resources to thingsboard - * #1 Return old Value Resource from LwM2MClient - * #2 Update new Resources (replace old Resource Value on new Resource Value) - * - * @param registration - Registration LwM2M Client - * @param value - LwM2mSingleResource response.getContent() - * @param values - LwM2mSingleResource response.getContent() - * @param path - resource - */ - private void onObservationSetResourcesValue(Registration registration, Object value, Map values, String path) { - boolean isChange = false; - try { - writeLock.lock(); - // #1 - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); - LwM2mPath pathIds = new LwM2mPath(path); -// log.warn("#0 nameDevice: [{}] resultIds: [{}] value: [{}], values: [{}] ", lwM2MClient.getDeviceName(), pathIds, value, values); - ResourceModel.Type resModelType = context.getCtxServer().getResourceModelType(registration, pathIds); - ResourceValue resValueOld = lwM2MClient.getResources().get(path); - // #2 - if (resValueOld.isMultiInstances() && !values.toString().equals(resValueOld.getResourceValue().toString())) { - lwM2MClient.getResources().get(path).setValues(values); -// ResourceValue resourceValue = new ResourceValue(values, null, true); -// lwM2MClient.getResources().put(path, resourceValue); - isChange = true; - } else if (!LwM2MTransportHandler.equalsResourceValue(resValueOld.getValue(), value, resModelType, pathIds)) { - lwM2MClient.getResources().get(path).setValue(value); -// ResourceValue resourceValueOld = lwM2MClient.getResources().get(path); -// lwM2MClient.getResources().remove(resourceValueOld); -// ResourceValue resourceValue = new ResourceValue(null, value, false); -// lwM2MClient.getResources().put(path, resourceValue); - log.warn("upDateResize: [{}] [{}] [{}] [{}]", lwM2MClient.getEndPoint(), lwM2MClient.getResources().size(), value, path); - isChange = true; - } - } catch (Exception e) { - log.error("#1_1 Update ResourcesValue after Observation is unsuccessfully path: [{}] value: [{}] [{}]", path, value, e.toString()); - } finally { - writeLock.unlock(); - } - - if (isChange) { - Set paths = new HashSet<>(); - paths.add(path); - this.updateAttrTelemetry(registration, false, paths); - } - } - - /** - * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) - * config attr/telemetry... in profile - */ - public void onToTransportUpdateCredentials(ToTransportUpdateCredentialsProto updateCredentials) { - log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); - } - - /** - * Update - sent request in change value resources in Client - * Path to resources from profile equal keyName or from ModelObject equal name - * Only for resources: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) - * Delete - nothing * - * - * @param msg - - */ - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { - if (msg.getSharedUpdatedCount() > 0) { - JsonElement el = JsonConverter.toJson(msg); - el.getAsJsonObject().entrySet().forEach(de -> { - String path = this.getPathAttributeUpdate(sessionInfo, de.getKey()); - String value = de.getValue().getAsString(); - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSession(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).entrySet().iterator().next().getValue(); - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - ResourceModel resourceModel = context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(path)); - if (!path.isEmpty() && (this.validatePathInAttrProfile(profile, path) || this.validatePathInTelemetryProfile(profile, path))) { - if (resourceModel != null && resourceModel.operations.isWritable()) { - lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), lwM2MClient, null, value, this.context.getCtxServer().getTimeout(), - false); - } else { - log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", path, value); - String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", path, value); - this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); - } - } else { - log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); - String logMsg = String.format(LOG_LW2M_ERROR + ": attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", de.getKey(), value); - this.sentLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); - } - }); - } else if (msg.getSharedDeletedCount() > 0) { - log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); - } - } - - /** - * Get path to resource from profile equal keyName or from ModelObject equal name - * Only for resource: isWritable && isPresent as attribute in profile -> AttrTelemetryObserveValue (format: CamelCase) - * - * @param sessionInfo - - * @param name - - * @return path if path isPresent in postProfile - */ - private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { - String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); -// return !profilePath.isEmpty() ? profilePath : this.getPathAttributeUpdateModelObject(name); - return !profilePath.isEmpty() ? profilePath : null; - } - - /** - * @param profile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInAttrProfile(AttrTelemetryObserveValue profile, String path) { - Set attributesSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); - return attributesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); - } - - /** - * @param profile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInTelemetryProfile(AttrTelemetryObserveValue profile, String path) { - Set telemetriesSet = new Gson().fromJson(profile.getPostTelemetryProfile(), Set.class); - return telemetriesSet.stream().filter(p -> p.equals(path)).findFirst().isPresent(); - } - - - /** - * Get path to resource from profile equal keyName - * - * @param sessionInfo - - * @param name - - * @return - - */ - private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { - AttrTelemetryObserveValue profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() - .filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey) - .orElse(""); - } - - /** - * Update resource (attribute) value on thingsboard after update value in client - * - * @param registration - - * @param path - - * @param request - - */ - public void onAttributeUpdateOk(Registration registration, String path, WriteRequest request, boolean isDelayedUpdate) { - ResourceModel resource = context.getCtxServer().getResourceModel(registration, new LwM2mPath(path)); - if (resource.multiple) { - this.onObservationSetResourcesValue(registration, null, ((LwM2mSingleResource) request.getNode()).getValues(), path); - } else { - this.onObservationSetResourcesValue(registration, ((LwM2mSingleResource) request.getNode()).getValue(), null, path); - } - if (isDelayedUpdate) lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null) - .onSuccessOrErrorDelayedRequests(request.getPath().toString()); - } - - /** - * @param sessionInfo - - * @param deviceProfile - + * @param parameters - JsonObject attributes/telemetry + * @param registration - Registration LwM2M Client */ - public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { - Set registrationIds = lwM2mInMemorySecurityStore.getSessions().entrySet() - .stream() - .filter(e -> e.getValue().getProfileUuid().equals(deviceProfile.getUuidId())) - .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); - if (registrationIds.size() > 0) { - this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); + private void addParameters(String path, JsonObject parameters, Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registration.getId()); + JsonObject names = lwM2mInMemorySecurityStore.getProfiles().get(lwM2MClient.getProfileUuid()).getPostKeyNameProfile(); + String resName = String.valueOf(names.get(path)); + if (resName != null && !resName.isEmpty()) { + try { + String resValue = this.getResourceValueToString(lwM2MClient, path); + if (resValue != null) { + parameters.addProperty(resName, resValue); + } + } catch (Exception e) { + log.error(e.getStackTrace().toString()); + } } } /** - * @param sessionInfo - - * @param device - - * @param deviceProfileOpt - + * @param path - path resource + * @return - value of Resource or null */ - public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { - Optional registrationIdOpt = lwM2mInMemorySecurityStore.getSessions().entrySet().stream() - .filter(e -> device.getUuidId().equals(e.getValue().getDeviceUuid())) - .map(Map.Entry::getKey) - .findFirst(); - registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); + private String getResourceValueToString(LwM2MClient lwM2MClient, String path) { + LwM2mPath pathIds = new LwM2mPath(path); + ResourceValue resourceValue = this.returnResourceValueFromLwM2MClient(lwM2MClient, pathIds); + return (resourceValue == null) ? null : + (String) this.converter.convertValue(resourceValue.getResourceValue(), this.context.getCtxServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds); } /** - * Update parameters device in LwM2MClient - * If new deviceProfile != old deviceProfile => update deviceProfile * - * @param registrationId - - * @param device - + * @param lwM2MClient - + * @param pathIds - + * @return - return value of Resource by idPath */ - private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { - LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getSessions().get(registrationId); - lwM2MClient.setDeviceName(device.getName()); - if (!lwM2MClient.getProfileUuid().equals(device.getDeviceProfileId().getId())) { - Set registrationIds = new HashSet<>(); - registrationIds.add(registrationId); - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); + private ResourceValue returnResourceValueFromLwM2MClient(LwM2MClient lwM2MClient, LwM2mPath pathIds) { + ResourceValue resourceValue = null; + if (pathIds.isResource()) { + resourceValue = lwM2MClient.getResources().get(pathIds.toString()); } + return resourceValue; + } - lwM2MClient.setProfileUuid(device.getDeviceProfileId().getId()); + /** + * Update resource (attribute) value on thingsboard after update value in client + * + * @param registration - + * @param path - + * @param request - + */ + public void onWriteResponseOk(Registration registration, String path, WriteRequest request) { + this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); } /** @@ -924,43 +791,43 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { */ private void onDeviceUpdateChangeProfile(Set registrationIds, DeviceProfile deviceProfile) { - AttrTelemetryObserveValue attrTelemetryObserveValueOld = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + LwM2MClientProfile lwM2MClientProfileOld = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); if (lwM2mInMemorySecurityStore.addUpdateProfileParameters(deviceProfile)) { // #1 - JsonArray attributeOld = attrTelemetryObserveValueOld.getPostAttributeProfile(); - Set attributeSetOld = new Gson().fromJson(attributeOld, Set.class); - JsonArray telemetryOld = attrTelemetryObserveValueOld.getPostTelemetryProfile(); - Set telemetrySetOld = new Gson().fromJson(telemetryOld, Set.class); - JsonArray observeOld = attrTelemetryObserveValueOld.getPostObserveProfile(); - JsonObject keyNameOld = attrTelemetryObserveValueOld.getPostKeyNameProfile(); - - AttrTelemetryObserveValue attrTelemetryObserveValueNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); - JsonArray attributeNew = attrTelemetryObserveValueNew.getPostAttributeProfile(); - Set attributeSetNew = new Gson().fromJson(attributeNew, Set.class); - JsonArray telemetryNew = attrTelemetryObserveValueNew.getPostTelemetryProfile(); - Set telemetrySetNew = new Gson().fromJson(telemetryNew, Set.class); - JsonArray observeNew = attrTelemetryObserveValueNew.getPostObserveProfile(); - JsonObject keyNameNew = attrTelemetryObserveValueNew.getPostKeyNameProfile(); + JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); + Set attributeSetOld = new Gson().fromJson(attributeOld, new TypeToken<>(){}.getType()); + JsonArray telemetryOld = lwM2MClientProfileOld.getPostTelemetryProfile(); + Set telemetrySetOld = new Gson().fromJson(telemetryOld, new TypeToken<>(){}.getType()); + JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); + JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); + + LwM2MClientProfile lwM2MClientProfileNew = lwM2mInMemorySecurityStore.getProfiles().get(deviceProfile.getUuidId()); + JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); + Set attributeSetNew = new Gson().fromJson(attributeNew, new TypeToken<>(){}.getType()); + JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); + Set telemetrySetNew = new Gson().fromJson(telemetryNew, new TypeToken<>(){}.getType()); + JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); + JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); // #3 ResultsAnalyzerParameters sentAttrToThingsboard = new ResultsAnalyzerParameters(); // #3.1 if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, Set.class), attributeSetNew); + ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, new TypeToken>(){}.getType()), attributeSetNew); sentAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); sentAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); } // #3.2 if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, Set.class), telemetrySetNew); + ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, new TypeToken>(){}.getType()), telemetrySetNew); sentAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); sentAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); } // #3.3 if (!keyNameOld.equals(keyNameNew)) { - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), ConcurrentHashMap.class), - new Gson().fromJson(keyNameNew.toString(), ConcurrentHashMap.class)); + ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), new TypeToken>(){}.getType()), + new Gson().fromJson(keyNameNew.toString(), new TypeToken>(){}.getType())); sentAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); } @@ -971,8 +838,7 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); LeshanServer lwServer = lwM2MClient.getLwServer(); Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); - log.warn("[{}] # 4.1", registration.getEndpoint()); - this.updateResourceValueObserve(lwServer, registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); + this.readResourceValueObserve(lwServer, registration, sentAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); // sent attr/telemetry to tingsboard for new path this.updateAttrTelemetry(registration, false, sentAttrToThingsboard.getPathPostParametersAdd()); }); @@ -985,8 +851,8 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { // #5.1 if (!observeOld.equals(observeNew)) { - Set observeSetOld = new Gson().fromJson(observeOld, Set.class); - Set observeSetNew = new Gson().fromJson(observeNew, Set.class); + Set observeSetOld = new Gson().fromJson(observeOld, new TypeToken<>(){}.getType()); + Set observeSetNew = new Gson().fromJson(observeNew, new TypeToken<>(){}.getType()); //#5.2 add // path Attr/Telemetry includes newObserve attributeSetOld.addAll(telemetrySetOld); @@ -1000,8 +866,7 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(null, registrationId); LeshanServer lwServer = lwM2MClient.getLwServer(); Registration registration = lwM2mInMemorySecurityStore.getByRegistration(registrationId); - log.warn("[{}] # 5.1", registration.getEndpoint()); - this.updateResourceValueObserve(lwServer, registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + this.readResourceValueObserve(lwServer, registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); // 5.3 del // sent Request cancel observe to Client this.cancelObserveIsValue(lwServer, registration, postObserveAnalyzer.getPathPostParametersDel()); @@ -1029,16 +894,6 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { return analyzerParameters; } - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - Set paths = keyNameNew.entrySet() - .stream() - .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); - analyzerParameters.setPathPostParametersAdd(paths); - return analyzerParameters; - } - private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); analyzerParameters.setPathPostParametersAdd(parametersObserve @@ -1054,77 +909,167 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { * @param registration - Registration LwM2M Client * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] */ - private void updateResourceValueObserve(LeshanServer lwServer, Registration registration, Set targets, String typeOper) { + private void readResourceValueObserve(LeshanServer lwServer, Registration registration, Set targets, String typeOper) { targets.forEach(target -> { LwM2mPath pathIds = new LwM2mPath(target); if (pathIds.isResource()) { if (GET_TYPE_OPER_READ.equals(typeOper)) { lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), - false); + ContentFormat.TLV.getName(), null, null, this.context.getCtxServer().getTimeout()); } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { lwM2MTransportRequest.sendAllRequest(lwServer, registration, target, typeOper, - null, null, null, null, this.context.getCtxServer().getTimeout(), - false); + null, null, null, this.context.getCtxServer().getTimeout()); } } }); } + private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentMap keyNameOld, ConcurrentMap keyNameNew) { + ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + Set paths = keyNameNew.entrySet() + .stream() + .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)).keySet(); + analyzerParameters.setPathPostParametersAdd(paths); + return analyzerParameters; + } + private void cancelObserveIsValue(LeshanServer lwServer, Registration registration, Set paramAnallyzer) { LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); paramAnallyzer.forEach(p -> { - if (this.getResourceValue(lwM2MClient, new LwM2mPath(p)) != null) { + if (this.returnResourceValueFromLwM2MClient(lwM2MClient, new LwM2mPath(p)) != null) { this.setCancelObservationRecourse(lwServer, registration, p); } } ); } - private ResourceValue getResourceValue(LwM2MClient lwM2MClient, LwM2mPath pathIds) { - ResourceValue resourceValue = null; - if (pathIds.isResource()) { - resourceValue = lwM2MClient.getResources().get(pathIds.toString()); + private void putDelayedUpdateResourcesClient(LwM2MClient lwM2MClient, Object valueOld, Object valueNew, String path) { + if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { + lwM2MTransportRequest.sendAllRequest(lwM2MClient.getLwServer(), lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, + ContentFormat.TLV.getName(), null, valueNew, this.context.getCtxServer().getTimeout()); + } else { + log.error("05 delayError"); } - return resourceValue; } /** - * Trigger Server path = "/1/0/8" + * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) + * config attr/telemetry... in profile + */ + public void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials) { + log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); + } + + /** + * Get path to resource from profile equal keyName or from ModelObject equal name + * Only for resource: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) * - * Trigger bootStrap path = "/1/0/9" - have to implemented on client + * @param sessionInfo - + * @param name - + * @return path if path isPresent in postProfile */ - public void doTrigger(LeshanServer lwServer, Registration registration, String path) { - lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_EXECUTE, - ContentFormat.TLV.getName(), null, null, null, this.context.getCtxServer().getTimeout(), - false); + private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { + String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); + return !profilePath.isEmpty() ? profilePath : null; } /** - * Session device in thingsboard is closed + * Get path to resource from profile equal keyName * - * @param sessionInfo - lwm2m client + * @param sessionInfo - + * @param name - + * @return - */ - private void doCloseSession(SessionInfoProto sessionInfo) { - TransportProtos.SessionEvent event = SessionEvent.CLOSED; - TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) - .setEvent(event).build(); - transportService.process(sessionInfo, msg, null); + private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { + LwM2MClientProfile profile = lwM2mInMemorySecurityStore.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() + .filter(e -> e.getValue().getAsString().equals(name)).findFirst().map(Map.Entry::getKey) + .orElse(""); } /** - * Deregister session in transport + * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values + * #1 Get path resource by result attributesResponse + * #1.1 If two names have equal path => last time attribute + * #2.1 if there is a difference in values between the current resource values and the shared attribute values + * => sent to client Request Update of value (new value from shared attribute) + * and LwM2MClient.delayedRequests.add(path) + * #2.1 if there is not a difference in values between the current resource values and the shared attribute values * - * @param sessionInfo - lwm2m client + * @param attributesResponse - + * @param sessionInfo - */ - public void doDisconnect(SessionInfoProto sessionInfo) { - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); - transportService.deregisterSession(sessionInfo); + public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { + try { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClient(sessionInfo); + attributesResponse.getSharedAttributeListList().forEach(attr -> { + String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); + // #1.1 + if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { + lwM2MClient.getDelayedRequests().put(path, attr); + } else { + lwM2MClient.getDelayedRequests().put(path, attr); + } + }); + // #2.1 + lwM2MClient.getDelayedRequests().forEach((k, v) -> { + ArrayList listV = new ArrayList<>(); + listV.add(v.getKv()); + this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); + }); + } catch (Exception e) { + log.error(String.valueOf(e)); + } } - private void checkInactivityAndReportActivity() { - lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); + /** + * @param lwM2MClient - + * @return + */ + private SessionInfoProto getNewSessionInfoProto(LwM2MClient lwM2MClient) { + if (lwM2MClient != null) { + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); + if (msg == null || msg.getDeviceInfo() == null) { + log.error("[{}] [{}]", lwM2MClient.getEndPoint(), CLIENT_NOT_AUTHORIZED); + this.closeClientSession(lwM2MClient.getRegistration()); + return null; + } else { + return SessionInfoProto.newBuilder() + .setNodeId(this.context.getNodeId()) + .setSessionIdMSB(lwM2MClient.getSessionUuid().getMostSignificantBits()) + .setSessionIdLSB(lwM2MClient.getSessionUuid().getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setDeviceName(msg.getDeviceInfo().getDeviceName()) + .setDeviceType(msg.getDeviceInfo().getDeviceType()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .build(); + } + } + return null; + } + + + /** + * @param registration - Registration LwM2M Client + * @return - sessionInfo after access connect client + */ + private SessionInfoProto getValidateSessionInfo(Registration registration) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(registration, null); + return getNewSessionInfoProto(lwM2MClient); + } + + /** + * @param registrationId - + * @return - + */ + private SessionInfoProto getValidateSessionInfo(String registrationId) { + LwM2MClient lwM2MClient = lwM2mInMemorySecurityStore.getLwM2MClientWithReg(null, registrationId); + return getNewSessionInfoProto(lwM2MClient); } /** @@ -1138,22 +1083,55 @@ public class LwM2MTransportServiceImpl implements LwM2MTransportService { } } - public void sentLogsToThingsboard(String msg, Registration registration) { - if (msg != null) { - JsonObject telemetries = new JsonObject(); - telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); - this.updateParametersOnThingsboard(telemetries, LwM2MTransportHandler.DEVICE_TELEMETRY_TOPIC, registration); + private void checkInactivityAndReportActivity() { + lwM2mInMemorySecurityStore.getSessions().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); + } + + /** + * If there is a difference in values between the current resource values and the shared attribute values + * when the client connects to the server + * #1 get attributes name from profile include name resources in ModelObject if resource isWritable + * #2.1 #1 size > 0 => send Request getAttributes to thingsboard + * + * @param lwM2MClient - LwM2M Client + */ + public void putDelayedUpdateResourcesThingsboard(LwM2MClient lwM2MClient) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + if (sessionInfo != null) { + //#1.1 + #1.2 + List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); + if (attrSharedNames.size() > 0) { + //#2.1 + try { + TransportProtos.GetAttributeRequestMsg getAttributeMsg = context.getAdaptor().convertToGetAttributes(null, attrSharedNames); + transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); + } catch (AdaptorException e) { + log.warn("Failed to decode get attributes request", e); + } + } } } + /** - * @param path - path resource - * @return - value of Resource or null + * Get names and keyNames from profile shared!!!! attr resources IsWritable + * + * @param lwM2MClient - + * @return ArrayList keyNames from profile attr resources shared!!!! && IsWritable */ - private String getResourceValueToString(LwM2MClient lwM2MClient, String path) { - LwM2mPath pathIds = new LwM2mPath(path); - ResourceValue resourceValue = this.getResourceValue(lwM2MClient, pathIds); - return (resourceValue == null) ? null : - (String) this.converter.convertValue(resourceValue.getResourceValue(), this.context.getCtxServer().getResourceModelType(lwM2MClient.getRegistration(), pathIds), ResourceModel.Type.STRING, pathIds); + private List getNamesAttrFromProfileIsWritable(LwM2MClient lwM2MClient) { + LwM2MClientProfile profile = lwM2mInMemorySecurityStore.getProfile(lwM2MClient.getProfileUuid()); + Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), Set.class); + ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), new TypeToken>(){}.getType()); + + ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() + .stream() + .filter(e -> (attrSet.contains(e.getKey()) && context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())) != null && + context.getCtxServer().getResourceModel(lwM2MClient.getRegistration(), new LwM2mPath(e.getKey())).operations.isWritable())) + .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); + + Set namesIsWritable = ConcurrentHashMap.newKeySet(); + namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); + return new ArrayList<>(namesIsWritable); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 937d2c7dcd..5ff826b25b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -108,7 +108,7 @@ public class LwM2mServerListener { @Override public void newObservation(Observation observation, Registration registration) { - log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); +// log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); } }; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java index 5ccfbff18a..f330968e97 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClient.java @@ -17,12 +17,9 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.response.LwM2mResponse; -import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; @@ -31,11 +28,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCreden import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportServiceImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; +import java.util.List; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; +import java.util.concurrent.CopyOnWriteArrayList; @Slf4j @Data @@ -44,7 +41,7 @@ public class LwM2MClient implements Cloneable { private String deviceProfileName; private String endPoint; private String identity; - private SecurityInfo info; + private SecurityInfo securityInfo; private UUID deviceUuid; private UUID sessionUuid; private UUID profileUuid; @@ -52,82 +49,52 @@ public class LwM2MClient implements Cloneable { private LwM2MTransportServiceImpl lwM2MTransportServiceImpl; private Registration registration; private ValidateDeviceCredentialsResponseMsg credentialsResponse; - private Map attributes; - private Map resources; - private Set pendingRequests; - private Map delayedRequests; - private Set delayedRequestsId; - private Map responses; + private final Map attributes; + private final Map resources; + private final Map delayedRequests; + private final List pendingRequests; + private boolean init; private final LwM2mValueConverterImpl converter; public Object clone() throws CloneNotSupportedException { return super.clone(); } - public LwM2MClient(String endPoint, String identity, SecurityInfo info, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileUuid) { + public LwM2MClient(String endPoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileUuid, UUID sessionUuid) { this.endPoint = endPoint; this.identity = identity; - this.info = info; + this.securityInfo = securityInfo; this.credentialsResponse = credentialsResponse; this.attributes = new ConcurrentHashMap<>(); - this.pendingRequests = ConcurrentHashMap.newKeySet(); this.delayedRequests = new ConcurrentHashMap<>(); + this.pendingRequests = new CopyOnWriteArrayList<>(); this.resources = new ConcurrentHashMap<>(); - this.delayedRequestsId = ConcurrentHashMap.newKeySet(); this.profileUuid = profileUuid; - /** - * Key , response instance -> resources: value...> - */ - this.responses = new ConcurrentHashMap<>(); + this.sessionUuid = sessionUuid; this.converter = LwM2mValueConverterImpl.getInstance(); + this.init = false; } - /** - * Fill with data -> Model client - * - * @param path - - * @param response - - */ - public void onSuccessHandler(String path, LwM2mResponse response) { - this.responses.put(path, response); - this.pendingRequests.remove(path); - if (this.pendingRequests.size() == 0) { - this.initValue(); - this.lwM2MTransportServiceImpl.putDelayedUpdateResourcesThingsboard(this); + public void updateResourceValue(String pathRez, LwM2mResource rez) { + if (rez instanceof LwM2mMultipleResource) { + this.resources.put(pathRez, new ResourceValue(rez.getValues(), null, true)); + } else if (rez instanceof LwM2mSingleResource) { + this.resources.put(pathRez, new ResourceValue(null, rez.getValue(), false)); } } - private void initValue() { - this.responses.forEach((key, resp) -> { - LwM2mPath pathIds = new LwM2mPath(key); - if (pathIds.isObject() || pathIds.isObjectInstance() || pathIds.isResource()) { - ObjectModel objectModel = this.lwServer.getModelProvider().getObjectModel(registration).getObjectModels().stream().filter(v -> v.id == pathIds.getObjectId()).collect(Collectors.toList()).get(0); - if (objectModel != null) { - ((LwM2mObjectInstance)((ReadResponse)resp).getContent()).getResources().forEach((k, v) -> { - String rez = pathIds.toString() + "/" + k; - if (((LwM2mObjectInstance) ((ReadResponse) resp).getContent()).getResource(k) instanceof LwM2mMultipleResource){ - this.resources.put(rez, new ResourceValue(v.getValues(), null, true)); - } - else { - this.resources.put(rez, new ResourceValue(null, v.getValue(), false)); - } - }); - } - } - }); - if (this.responses.size() == 0) this.responses = new ConcurrentHashMap<>(); - } - - /** - * if path != null - * @param path - */ - public void onSuccessOrErrorDelayedRequests(String path) { - if (path != null) this.delayedRequests.remove(path); - if (this.delayedRequests.size() == 0 && this.getDelayedRequestsId().size() == 0) { - this.lwM2MTransportServiceImpl.updatesAndSentModelParameter(this); + public void initValue(LwM2MTransportServiceImpl lwM2MTransportService, String path) { + if (path != null) { + this.pendingRequests.remove(path); + } + if (this.pendingRequests.size() == 0) { + this.init = true; + lwM2MTransportService.putDelayedUpdateResourcesThingsboard(this); } } + public LwM2MClient copy() { + return new LwM2MClient(this.endPoint, this.identity, this.securityInfo, this.credentialsResponse, this.profileUuid, this.sessionUuid); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/AttrTelemetryObserveValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientProfile.java similarity index 86% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/AttrTelemetryObserveValue.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientProfile.java index ab8dacc56c..77f7a67646 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/AttrTelemetryObserveValue.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientProfile.java @@ -20,7 +20,14 @@ import com.google.gson.JsonObject; import lombok.Data; @Data -public class AttrTelemetryObserveValue { +public class LwM2MClientProfile { + /** + * {"clientLwM2mSettings": { + * clientUpdateValueAfterConnect: false; + * } + **/ + JsonObject postClientLwM2mSettings; + /** * {"keyName": { * "/3/0/1": "modelNumber", diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java index 5f0e82126b..06a37a3290 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/secure/LwM2mInMemorySecurityStore.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.secure; -import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.californium.LeshanServer; @@ -32,8 +31,8 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2MTransportHandler; -import org.thingsboard.server.transport.lwm2m.server.client.AttrTelemetryObserveValue; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientProfile; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import java.util.Collection; @@ -60,7 +59,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { private final Lock readLock = readWriteLock.readLock(); private final Lock writeLock = readWriteLock.writeLock(); private final Map sessions = new ConcurrentHashMap<>(); - private Map profiles = new ConcurrentHashMap<>(); + private Map profiles = new ConcurrentHashMap<>(); private SecurityStoreListener listener; @Autowired @@ -75,9 +74,9 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { public SecurityInfo getByEndpoint(String endPoint) { readLock.lock(); try { - String registrationId = this.getByRegistrationId(endPoint, null); + String registrationId = this.getRegistrationId(endPoint, null); return (registrationId != null && sessions.size() > 0 && sessions.get(registrationId) != null) ? - sessions.get(registrationId).getInfo() : this.addLwM2MClientToSession(endPoint); + sessions.get(registrationId).getSecurityInfo() : this.addLwM2MClientToSession(endPoint); } finally { readLock.unlock(); } @@ -92,8 +91,8 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { public SecurityInfo getByIdentity(String identity) { readLock.lock(); try { - String integrationId = this.getByRegistrationId(null, identity); - return (integrationId != null) ? sessions.get(integrationId).getInfo() : this.addLwM2MClientToSession(identity); + String integrationId = this.getRegistrationId(null, identity); + return (integrationId != null) ? sessions.get(integrationId).getSecurityInfo() : this.addLwM2MClientToSession(identity); } finally { readLock.unlock(); } @@ -103,7 +102,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { public Collection getAll() { readLock.lock(); try { - return Collections.unmodifiableCollection(this.sessions.values().stream().map(LwM2MClient::getInfo).collect(Collectors.toList())); + return Collections.unmodifiableCollection(this.sessions.values().stream().map(LwM2MClient::getSecurityInfo).collect(Collectors.toList())); } finally { readLock.unlock(); } @@ -119,7 +118,7 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { LwM2MClient lwM2MClient = (sessions.get(registrationId) != null) ? sessions.get(registrationId) : null; if (lwM2MClient != null) { if (listener != null) { - listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getInfo()); + listener.securityInfoRemoved(INFOS_ARE_COMPROMISED, lwM2MClient.getSecurityInfo()); } sessions.remove(registrationId); } @@ -169,19 +168,19 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { LwM2MClient lwM2MClient = this.sessions.get(registration.getEndpoint()); lwM2MClient.setLwServer(lwServer); lwM2MClient.setRegistration(registration); - lwM2MClient.setAttributes(registration.getAdditionalRegistrationAttributes()); - this.sessions.put(registration.getId(), lwM2MClient); + lwM2MClient.getAttributes().putAll(registration.getAdditionalRegistrationAttributes()); this.sessions.remove(registration.getEndpoint()); + this.sessions.put(registration.getId(), lwM2MClient); return lwM2MClient; } finally { writeLock.unlock(); } } - private String getByRegistrationId(String endPoint, String identity) { + private String getRegistrationId(String endPoint, String identity) { List registrationIds = (endPoint != null) ? - this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndPoint())).map(model -> model.getKey()).collect(Collectors.toList()) : - this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).map(model -> model.getKey()).collect(Collectors.toList()); + this.sessions.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndPoint())).map(Map.Entry::getKey).collect(Collectors.toList()) : + this.sessions.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).map(Map.Entry::getKey).collect(Collectors.toList()); return (registrationIds != null && registrationIds.size() > 0) ? registrationIds.get(0) : null; } @@ -204,9 +203,9 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; if (store.getSecurityInfo() != null && profileUuid != null) { String endpoint = store.getSecurityInfo().getEndpoint(); - sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid)); + sessions.put(endpoint, new LwM2MClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid, UUID.randomUUID())); } else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) { - sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), profileUuid)); + sessions.put(identity, new LwM2MClient(identity, null, null, store.getMsg(), profileUuid, UUID.randomUUID())); } else { log.error("Registration failed: FORBIDDEN/profileUuid/device [{}] , endpointId: [{}]", profileUuid, identity); /** @@ -228,23 +227,29 @@ public class LwM2mInMemorySecurityStore extends InMemorySecurityStore { return this.sessions; } - public Map getProfiles() { + public Map getProfiles() { return this.profiles; } - public AttrTelemetryObserveValue getProfile(UUID profileUuId) { + public LwM2MClientProfile getProfile(UUID profileUuId) { return this.profiles.get(profileUuId); } - public Map setProfiles(Map profiles) { + public LwM2MClientProfile getProfile(String registrationId) { + UUID profileUUid = this.getSessions().get(registrationId).getProfileUuid(); + return this.getProfiles().get(profileUUid); + } + + public Map setProfiles(Map profiles) { return this.profiles = profiles; } public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { - JsonObject profilesConfigData = LwM2MTransportHandler.getObserveAttrTelemetryFromThingsboard(deviceProfile); - if (profilesConfigData != null) { - profiles.put(deviceProfile.getUuidId(), LwM2MTransportHandler.getNewProfileParameters(profilesConfigData)); + LwM2MClientProfile lwM2MClientProfile = LwM2MTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile); + if (lwM2MClientProfile != null) { + profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); + return true; } - return (profilesConfigData != null); + return false; } } diff --git a/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks b/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks index 9336dd1168..9f6748f8fd 100644 Binary files a/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks and b/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks differ diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh index 3167cca2b5..dcf38b4f10 100755 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # Copyright © 2016-2021 The Thingsboard Authors # @@ -15,77 +15,35 @@ # limitations under the License. # -#p) CLIENT_CN=LwX50900000000 +#/home/nick/Igor_project/Thingsboard_Perfrmance_test/performance-tests/src/main/resources/credentials/shell/lwM2M_credentials.sh -p LwX509 -s 0 -f 2000 -a client_alias_ -e client_self_signed_ -b bootstrap -d server -j serverKeyStore.jks -k clientKeyStore.jks -c client_ks_password -w server_ks_password + +#p) CLIENT_CN=$CLIENT_PREFIX00000000 #s) client_start=0 #f) client_finish=1 -#a) CLIENT_ALIAS=client_alias_00000000 +#a) CLIENT_ALIAS=CLIENT_ALIAS_PREFIX_00000000 +#e) CLIENT_SELF_ALIAS=CLIENT_SELF_ALIAS_PREFIX_00000000 #b) BOOTSTRAP_ALIAS=bootstrap #d) SERVER_ALIAS=server #j) SERVER_STORE=serverKeyStore.jks #k) CLIENT_STORE=clientKeyStore.jks #c) CLIENT_STORE_PWD=client_ks_password #w) SERVER_STORE_PWD=server_ks_password +#l) ROOT_KEY_ALIAS=root_key_alias -#while test $# -gt 0; do -# case "$1" in -# -h|--help) -# echo "$package - attempt to capture frames" -# echo " " -# echo "$package [options] application [arguments]" -# echo " " -# echo "options:" -# echo "-h, --help show brief help" -# echo "-a, --action=ACTION specify an action to use" -# echo "-o, --output-dir=DIR specify a directory to store output in" -# exit 0 -# ;; -# -a) -# shift -# if test $# -gt 0; then -# export PROCESS=$1 -# else -# echo "no process specified" -# exit 1 -# fi -# shift -# ;; -# --action*) -# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` -# shift -# ;; -# -o) -# shift -# if test $# -gt 0; then -# export OUTPUT=$1 -# else -# echo "no output dir specified" -# exit 1 -# fi -# shift -# ;; -# --output-dir*) -# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` -# shift -# ;; -# *) -# break -# ;; -# esac -#done - - -while getopts p:s:f:a:b:d:j:k:c:w: flag; do +while getopts p:s:f:a:e:b:d:j:k:c:w:l: flag; do case "${flag}" in - p) client_prefix=${OPTARG} ;; + p) client_pref=${OPTARG} ;; s) client_start=${OPTARG} ;; f) client_finish=${OPTARG} ;; - a) client_alias=${OPTARG} ;; + a) client_alias_pref=${OPTARG} ;; + e) client_self_alias_pref=${OPTARG} ;; b) bootstrap_alias=${OPTARG} ;; d) server_alias=${OPTARG} ;; j) key_store_server_file=${OPTARG} ;; k) key_store_client_file=${OPTARG} ;; c) client_key_store_pwd=${OPTARG} ;; w) server_key_store_pwd=${OPTARG} ;; + w) root_key_alias=${OPTARG} ;; esac done @@ -96,9 +54,8 @@ cd $script_dir # source the properties: . ./lwM2M_keygen.properties - -if [ -n "$client_prefix" ]; then - CLIENT_PREFIX=$client_prefix +if [ -n "$client_pref" ]; then + CLIENT_PREFIX=$client_pref fi if [ -z "$client_start" ]; then @@ -109,8 +66,12 @@ if [ -z "$client_finish" ]; then client_finish=1 fi -if [ -n "$client_alias" ]; then - CLIENT_ALIAS=$client_alias +if [ -n "$client_alias_pref" ]; then + CLIENT_ALIAS_PREFIX=$client_alias_pref +fi + +if [ -n "$client_self_alias_pref" ]; then + CLIENT_SELF_ALIAS_PREFIX=$client_self_alias_pref fi if [ -n "$bootstrap_alias" ]; then @@ -137,23 +98,37 @@ if [ -n "$server_key_store_pwd" ]; then SERVER_STORE_PWD=$server_key_store_pwd fi +if [ -n "$root_key_alias" ]; then + ROOT_KEY_ALIAS=$root_key_alias +fi + +CLIENT_NUMBER=$client_start + echo "==Start==" echo "CLIENT_PREFIX: $CLIENT_PREFIX" echo "client_start: $client_start" echo "client_finish: $client_finish" -echo "CLIENT_ALIAS: $CLIENT_ALIAS" +echo "CLIENT_ALIAS_PREFIX: $CLIENT_ALIAS_PREFIX" +echo "CLIENT_SELF_ALIAS_PREFIX: $CLIENT_SELF_ALIAS_PREFIX" echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS" echo "SERVER_ALIAS: $SERVER_ALIAS" echo "SERVER_STORE: $SERVER_STORE" echo "CLIENT_STORE: $CLIENT_STORE" echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD" echo "SERVER_STORE_PWD: $SERVER_STORE_PWD" +echo "CLIENT_NUMBER: $CLIENT_NUMBER" +echo "ROOT_KEY_ALIAS: $ROOT_KEY_ALIAS" end_point() { echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)" } + client_alias_point() { - echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)" + echo "$CLIENT_ALIAS_PREFIX$(printf "%08d" $CLIENT_NUMBER)" +} + +client_self_alias_point() { + echo "$CLIENT_SELF_ALIAS_PREFIX$(printf "%08d" $CLIENT_NUMBER)" } # Generation of the keystore. @@ -264,73 +239,30 @@ keytool \ -keystore $SERVER_STORE \ -storepass $SERVER_STORE_PWD -echo -echo "${H1}Client Keystore : ${RESET}" -echo "${H1}==================${RESET}" -#echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" -#keytool \ -# -genkeypair \ -# -alias $CLIENT_ALIAS \ -# -keyalg EC \ -# -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -# -validity $VALIDITY \ -# -storetype $STORETYPE \ -# -keypass $CLIENT_STORE_PWD \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD -#keytool \ -# -exportcert \ -# -alias $CLIENT_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD | \ -# keytool \ -# -importcert \ -# -alias $CLIENT_SELF_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD \ -# -noprompt - -echo -echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}" -keytool \ - -exportcert \ - -alias $ROOT_KEY_ALIAS \ - -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | +if [ "$client_start" -lt "$client_finish" ]; then + echo + echo "${H2}Import root certificate just to be able to import need by root CA with expected CN to $CLIENT_STORE${RESET}" keytool \ - -importcert \ + -exportcert \ -alias $ROOT_KEY_ALIAS \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD \ - -noprompt - -#echo -#echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" -#keytool \ -# -certreq \ -# -alias $CLIENT_ALIAS \ -# -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD | \ -# keytool \ -# -gencert \ -# -alias $ROOT_KEY_ALIAS \ -# -keystore $SERVER_STORE \ -# -storepass $SERVER_STORE_PWD \ -# -storetype $STORETYPE \ -# -validity $VALIDITY | \ -# keytool \ -# -importcert \ -# -alias $CLIENT_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD \ -# -noprompt + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -importcert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt +fi cert_end_point() { - echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}" + echo + echo "${H1}Client Keystore : ${RESET}" + echo "${H1}==================${RESET}" + echo "${H2}Creating client key and self-signed certificate with expected CN CLIENT_ALIAS: $CLIENT_ALIAS${RESET}" keytool \ -genkeypair \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keyalg EC \ -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -validity $VALIDITY \ @@ -340,7 +272,7 @@ cert_end_point() { -storepass $CLIENT_STORE_PWD keytool \ -exportcert \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD | keytool \ @@ -349,13 +281,28 @@ cert_end_point() { -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD \ -noprompt +# +# echo +# echo "${H2}Import root certificate just to be able to import ned by root CA with expected CN...${RESET}" +# keytool \ +# -exportcert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $SERVER_STORE \ +# -storepass $SERVER_STORE_PWD | +# keytool \ +# -importcert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt +# echo - echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}" + echo "${H2}Creating client certificate signed by root CA with expected CN CLIENT_ALIAS: $CLIENT_ALIAS CLIENT_CN: $CLIENT_CN${RESET}" keytool \ -certreq \ - -alias $CLIENT_CN_ALIAS \ - -dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -alias $CLIENT_ALIAS \ + -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD | keytool \ @@ -367,22 +314,30 @@ cert_end_point() { -validity $VALIDITY | keytool \ -importcert \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD \ -noprompt } -while [ "$CLIENT_NUMBER" != "$client_finish" ]; do - CLIENT_CN_NAME=$(end_point) - CLIENT_CN_ALIAS=$(client_alias_point) - echo "$CLIENT_CN_NAME" - echo "$CLIENT_CN_ALIAS" - cert_end_point - CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) - echo "number $CLIENT_NUMBER" - echo "finish $client_finish" -done +if [ "$client_start" -lt "$client_finish" ]; then + echo "Файл содержит, как минимум, одно слово Bash." + echo + echo "==Start Client==" + while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do + echo "number $CLIENT_NUMBER" + echo "finish $client_finish" + CLIENT_CN=$(end_point) + CLIENT_ALIAS=$(client_alias_point) + CLIENT_SELF_ALIAS=$(client_self_alias_point) + echo "CLIENT_CN $CLIENT_CN" + echo "CLIENT_ALIAS $CLIENT_ALIAS" + echo "CLIENT_SELF_ALIAS $CLIENT_SELF_ALIAS" + cert_end_point + CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) + echo + done +fi echo echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" @@ -393,11 +348,13 @@ keytool \ -deststoretype pkcs12 \ -srcstorepass $SERVER_STORE_PWD -echo -echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" -keytool \ - -importkeystore \ - -srckeystore $CLIENT_STORE \ - -destkeystore $CLIENT_STORE \ - -deststoretype pkcs12 \ - -srcstorepass $CLIENT_STORE_PWD +if [ "$client_start" -lt "$client_finish" ]; then + echo + echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" + keytool \ + -importkeystore \ + -srckeystore $CLIENT_STORE \ + -destkeystore $CLIENT_STORE \ + -deststoretype pkcs12 \ + -srcstorepass $CLIENT_STORE_PWD +fi diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties index 18f851e4dd..7b3cd9c09a 100644 --- a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties +++ b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_keygen.properties @@ -41,9 +41,9 @@ BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed" # Client CLIENT_STORE=clientKeyStore1.jks CLIENT_STORE_PWD=client_ks_password1 -CLIENT_ALIAS=client_alias_1 -CLIENT_PREFIX=LwX509_ -CLIENT_SELF_ALIAS=client_self_signed +CLIENT_ALIAS_PREFIX=client_alias_1 +CLIENT_PREFIX=LwX509___ +CLIENT_SELF_ALIAS_PREFIX=client_self_signed_1 CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed" # Color output stuff diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 18c2126f07..676593804e 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -90,7 +90,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index ec4247b718..c2cf3686e9 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -45,6 +45,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.security.KeyStore; +import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.concurrent.CountDownLatch; @@ -154,7 +155,7 @@ public class MqttSslHandlerProvider { String credentialsBody = null; for (X509Certificate cert : chain) { try { - String strCert = SslUtil.getX509CertificateString(cert); + String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); final String[] credentialsBodyHolder = new String[1]; CountDownLatch latch = new CountDownLatch(1); @@ -179,7 +180,7 @@ public class MqttSslHandlerProvider { credentialsBody = credentialsBodyHolder[0]; break; } - } catch (InterruptedException | IOException e) { + } catch (InterruptedException | CertificateEncodingException e) { log.error(e.getMessage(), e); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 8a8fcff2a4..06a8dcacdc 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -35,6 +35,7 @@ import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import io.netty.handler.codec.mqtt.MqttTopicSubscription; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import io.netty.handler.ssl.SslHandler; +import io.netty.util.CharsetUtil; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; @@ -68,7 +69,8 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; import org.thingsboard.server.transport.mqtt.util.SslUtil; import javax.net.ssl.SSLPeerUnverifiedException; -import javax.security.cert.X509Certificate; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.io.IOException; import java.net.InetSocketAddress; import java.util.ArrayList; @@ -315,7 +317,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private TransportServiceCallback getPubAckCallback(final ChannelHandlerContext ctx, final int msgId, final T msg) { - return new TransportServiceCallback() { + return new TransportServiceCallback<>() { @Override public void onSuccess(Void dummy) { log.trace("[{}] Published msg: {}", sessionId, msg); @@ -482,12 +484,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (userName != null) { request.setUserName(userName); } - String password = connectMessage.payload().password(); - if (password != null) { + byte[] passwordBytes = connectMessage.payload().passwordInBytes(); + if (passwordBytes != null) { + String password = new String(passwordBytes, CharsetUtil.UTF_8); request.setPassword(password); } transportService.process(DeviceTransportType.MQTT, request.build(), - new TransportServiceCallback() { + new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { onValidateDeviceResponse(msg, ctx, connectMessage); @@ -507,10 +510,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (!context.isSkipValidityCheckForClientCert()) { cert.checkValidity(); } - String strCert = SslUtil.getX509CertificateString(cert); + String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); transportService.process(DeviceTransportType.MQTT, ValidateDeviceX509CertRequestMsg.newBuilder().setHash(sha3Hash).build(), - new TransportServiceCallback() { + new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { onValidateDeviceResponse(msg, ctx, connectMessage); @@ -531,9 +534,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private X509Certificate getX509Certificate() { try { - X509Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificateChain(); + Certificate[] certChain = sslHandler.engine().getSession().getPeerCertificates(); if (certChain.length > 0) { - return certChain[0]; + return (X509Certificate) certChain[0]; } } catch (SSLPeerUnverifiedException e) { log.warn(e.getMessage()); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java index bd0d034c35..f376077b84 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/SslUtil.java @@ -20,8 +20,8 @@ import org.springframework.util.Base64Utils; import org.thingsboard.server.common.msg.EncryptionUtil; import java.io.IOException; +import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; -import java.security.cert.X509Certificate; /** * @author Valerii Sosliuk @@ -32,15 +32,8 @@ public class SslUtil { private SslUtil() { } - public static String getX509CertificateString(X509Certificate cert) - throws CertificateEncodingException, IOException { - Base64Utils.encodeToString(cert.getEncoded()); - return EncryptionUtil.trimNewLines(Base64Utils.encodeToString(cert.getEncoded())); - } - - public static String getX509CertificateString(javax.security.cert.X509Certificate cert) - throws javax.security.cert.CertificateEncodingException, IOException { - Base64Utils.encodeToString(cert.getEncoded()); + public static String getCertificateString(Certificate cert) + throws CertificateEncodingException { return EncryptionUtil.trimNewLines(Base64Utils.encodeToString(cert.getEncoded())); } } diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 178d174543..cc59d8bd0b 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.mqtt.util; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import javax.script.ScriptException; diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 6e8f9e10ec..c4f042d67d 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -87,7 +87,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index d81344fe5c..ea20f6fb50 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -51,6 +51,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -248,12 +249,25 @@ public class JsonConverter { } private static void parseNumericValue(List result, Entry valueEntry, JsonPrimitive value) { - if (value.getAsString().contains(".")) { - result.add(new DoubleDataEntry(valueEntry.getKey(), value.getAsDouble())); + String valueAsString = value.getAsString(); + String key = valueEntry.getKey(); + if (valueAsString.contains("e") || valueAsString.contains("E")) { + var bd = new BigDecimal(valueAsString); + if (bd.stripTrailingZeros().scale() <= 0) { + try { + result.add(new LongDataEntry(key, bd.longValueExact())); + } catch (ArithmeticException e) { + result.add(new DoubleDataEntry(key, bd.doubleValue())); + } + } else { + result.add(new DoubleDataEntry(key, bd.doubleValue())); + } + } else if (valueAsString.contains(".")) { + result.add(new DoubleDataEntry(key, value.getAsDouble())); } else { try { long longValue = Long.parseLong(value.getAsString()); - result.add(new LongDataEntry(valueEntry.getKey(), longValue)); + result.add(new LongDataEntry(key, longValue)); } catch (NumberFormatException e) { throw new JsonSyntaxException("Big integer values are not supported!"); } @@ -288,7 +302,8 @@ public class JsonConverter { return result; } - public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) { + public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg + responseMsg) { JsonObject result = new JsonObject(); result.addProperty("id", responseMsg.getRequestId()); result.addProperty(DEVICE_PROPERTY, deviceName); @@ -301,7 +316,8 @@ public class JsonConverter { return result; } - public static JsonObject getJsonObjectForGateway(String deviceName, AttributeUpdateNotificationMsg notificationMsg) { + public static JsonObject getJsonObjectForGateway(String deviceName, AttributeUpdateNotificationMsg + notificationMsg) { JsonObject result = new JsonObject(); result.addProperty(DEVICE_PROPERTY, deviceName); result.add("data", toJson(notificationMsg)); @@ -451,7 +467,8 @@ public class JsonConverter { return result; } - public static JsonElement toGatewayJson(String deviceName, TransportProtos.ProvisionDeviceResponseMsg responseRequest) { + public static JsonElement toGatewayJson(String deviceName, TransportProtos.ProvisionDeviceResponseMsg + responseRequest) { JsonObject result = new JsonObject(); result.addProperty(DEVICE_PROPERTY, deviceName); result.add("data", JsonConverter.toJson(responseRequest)); @@ -501,15 +518,18 @@ public class JsonConverter { return result; } - public static Map> convertToTelemetry(JsonElement jsonElement, long systemTs) throws JsonSyntaxException { + public static Map> convertToTelemetry(JsonElement jsonElement, long systemTs) throws + JsonSyntaxException { return convertToTelemetry(jsonElement, systemTs, false); } - public static Map> convertToSortedTelemetry(JsonElement jsonElement, long systemTs) throws JsonSyntaxException { + public static Map> convertToSortedTelemetry(JsonElement jsonElement, long systemTs) throws + JsonSyntaxException { return convertToTelemetry(jsonElement, systemTs, true); } - public static Map> convertToTelemetry(JsonElement jsonElement, long systemTs, boolean sorted) throws JsonSyntaxException { + public static Map> convertToTelemetry(JsonElement jsonElement, long systemTs, boolean sorted) throws + JsonSyntaxException { Map> result = sorted ? new TreeMap<>() : new HashMap<>(); convertToTelemetry(jsonElement, systemTs, result, null); return result; @@ -579,7 +599,8 @@ public class JsonConverter { .build(); } - private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String + provisionKey, String provisionSecret) { return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey(provisionKey) .setProvisionDeviceSecret(provisionSecret) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java index 48f31edc97..14da88ecd1 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java @@ -39,49 +39,21 @@ public class LwM2MTransportConfigBootstrap { @Value("${transport.lwm2m.bootstrap.id:}") private Integer bootstrapServerId; - @Getter - @Value("${transport.lwm2m.bootstrap.secure.start_psk:}") - private Boolean bootstrapStartPsk; - - @Getter - @Value("${transport.lwm2m.bootstrap.secure.start_rpk:}") - private Boolean bootstrapStartRpk; - - @Getter - @Value("${transport.lwm2m.bootstrap.secure.start_x509:}") - private Boolean bootstrapStartX509; - @Getter @Value("${transport.lwm2m.bootstrap.bind_address:}") private String bootstrapHost; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_address:}") - private String bootstrapSecureHost; - - @Getter - @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_psk:}") - private Integer bootstrapPortNoSecPsk; - - @Getter - @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_rpk:}") - private Integer bootstrapPortNoSecRpk; - - @Getter - @Value("${transport.lwm2m.bootstrap.bind_port_no_sec_x509:}") - private Integer bootstrapPortNoSecX509; - - @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port_psk:}") - private Integer bootstrapSecurePortPsk; + @Value("${transport.lwm2m.bootstrap.bind_port_no_sec:}") + private Integer bootstrapPortNoSec; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port_rpk:}") - private Integer bootstrapSecurePortRpk; + @Value("${transport.lwm2m.bootstrap.secure.bind_address_security:}") + private String bootstrapHostSecurity; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port_x509:}") - private Integer bootstrapSecurePortX509; + @Value("${transport.lwm2m.bootstrap.secure.bind_port_security:}") + private Integer bootstrapPortSecurity; @Getter @Value("${transport.lwm2m.bootstrap.secure.public_x:}") @@ -96,8 +68,8 @@ public class LwM2MTransportConfigBootstrap { private PublicKey bootstrapPublicKey; @Getter - @Value("${transport.lwm2m.bootstrap.secure.private_s:}") - private String bootstrapPrivateS; + @Value("${transport.lwm2m.bootstrap.secure.private_encoded:}") + private String bootstrapPrivateEncoded; @Getter @Value("${transport.lwm2m.bootstrap.secure.alias:}") diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java index dfe2b75fef..248475a002 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java @@ -79,7 +79,7 @@ public class LwM2MTransportConfigServer { private String BASE_DIR_PATH = System.getProperty("user.dir"); @Getter -// private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$"; + // private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$"; private String PATH_DATA = "data"; @Getter @@ -147,57 +147,28 @@ public class LwM2MTransportConfigServer { private String rootAlias; @Getter - @Value("${transport.lwm2m.server.secure.start_psk:}") - private boolean serverStartPsk; - - @Getter - @Value("${transport.lwm2m.server.secure.start_rpk:}") - private boolean serverStartRpk; - - @Getter - @Value("${transport.lwm2m.server.secure.start_x509:}") - private boolean serverStartX509; - - @Getter - @Value("${transport.lwm2m.secure.enable_gen_psk_rpk:}") - private Boolean enableGenPskRpk; - - @Getter - @Value("${transport.lwm2m.server.bind_address:}") - private String serverHost; + @Value("${transport.lwm2m.secure.enable_gen_new_key_psk_rpk:}") + private Boolean enableGenNewKeyPskRpk; @Getter @Value("${transport.lwm2m.server.id:}") private Integer serverId; @Getter - @Value("${transport.lwm2m.server.secure.bind_address:}") - private String serverSecureHost; - - - @Getter - @Value("${transport.lwm2m.server.bind_port_no_sec_psk:}") - private Integer serverPortNoSecPsk; - - @Getter - @Value("${transport.lwm2m.server.bind_port_no_sec_rpk:}") - private Integer serverPortNoSecRpk; - - @Getter - @Value("${transport.lwm2m.server.bind_port_no_sec_x509:}") - private Integer serverPortNoSecX509; + @Value("${transport.lwm2m.server.bind_address:}") + private String serverHost; @Getter - @Value("${transport.lwm2m.server.secure.bind_port_psk:}") - private Integer serverPortPsk; + @Value("${transport.lwm2m.server.secure.bind_address_security:}") + private String serverHostSecurity; @Getter - @Value("${transport.lwm2m.server.secure.bind_port_rpk:}") - private Integer serverPortRpk; + @Value("${transport.lwm2m.server.bind_port_no_sec:}") + private Integer serverPortNoSec; @Getter - @Value("${transport.lwm2m.server.secure.bind_port_x509:}") - private Integer serverPortX509; + @Value("${transport.lwm2m.server.secure.bind_port_security:}") + private Integer serverPortSecurity; @Getter @Value("${transport.lwm2m.server.secure.public_x:}") @@ -208,8 +179,8 @@ public class LwM2MTransportConfigServer { private String serverPublicY; @Getter - @Value("${transport.lwm2m.server.secure.private_s:}") - private String serverPrivateS; + @Value("${transport.lwm2m.server.secure.private_encoded:}") + private String serverPrivateEncoded; @Getter @Value("${transport.lwm2m.server.secure.alias:}") @@ -303,7 +274,4 @@ public class LwM2MTransportConfigServer { ResourceModel resource = this.getResourceModel(registration, pathIds); return (resource == null) ? ResourceModel.Operations.NONE : resource.operations; } - - - } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 893a93e31a..473eeea42c 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -784,8 +784,8 @@ public class DefaultTransportService implements TransportService { wrappedCallback); } - protected void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tbMsg.getOriginator()); + private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, tbMsg.getOriginator()); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg); } @@ -797,7 +797,7 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, + private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java index 73ff79634d..a3b65c2248 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java @@ -32,6 +32,7 @@ public class ProtoWithFSTService implements DataDecodingEncodingService { @Override public Optional decode(byte[] byteArray) { try { + @SuppressWarnings("unchecked") T msg = (T) config.asObject(byteArray); return Optional.of(msg); } catch (IllegalArgumentException e) { diff --git a/common/transport/transport-api/src/test/java/JsonConverterTest.java b/common/transport/transport-api/src/test/java/JsonConverterTest.java new file mode 100644 index 0000000000..cedbef50c9 --- /dev/null +++ b/common/transport/transport-api/src/test/java/JsonConverterTest.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.gson.JsonParser; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; + +@RunWith(MockitoJUnitRunner.class) +public class JsonConverterTest { + + private static final JsonParser JSON_PARSER = new JsonParser(); + + @Test + public void testParseBigDecimalAsLong() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1E+1}"), 0L); + Assert.assertEquals(10L, result.get(0L).get(0).getLongValue().get().longValue()); + } + + @Test + public void testParseBigDecimalAsDouble() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 101E-1}"), 0L); + Assert.assertEquals(10.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); + } + + @Test + public void testParseAsDouble() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 1.1}"), 0L); + Assert.assertEquals(1.1, result.get(0L).get(0).getDoubleValue().get(), 0.0); + } + + @Test + public void testParseAsLong() { + var result = JsonConverter.convertToTelemetry(JSON_PARSER.parse("{\"meterReadingDelta\": 11}"), 0L); + Assert.assertEquals(11L, result.get(0L).get(0).getLongValue().get().longValue()); + } + +} diff --git a/common/util/pom.xml b/common/util/pom.xml index 68ad14d2eb..13afe4663a 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -41,6 +41,10 @@ guava provided + + javax.annotation + javax.annotation-api + org.slf4j slf4j-api @@ -64,7 +68,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/dao/pom.xml b/dao/pom.xml index e9ee4e9219..cbd3899f25 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -92,7 +92,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 445cf1c897..8f7071eadc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -40,11 +40,11 @@ public abstract class DaoUtil { public static PageData toPageData(Page> page) { List data = convertDataList(page.getContent()); - return new PageData(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); + return new PageData<>(data, page.getTotalPages(), page.getTotalElements(), page.hasNext()); } public static PageData pageToPageData(Page page) { - return new PageData(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); + return new PageData<>(page.getContent(), page.getTotalPages(), page.getTotalElements(), page.hasNext()); } public static Pageable toPageable(PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 6c12b5f753..ebbfae0f05 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -19,16 +19,18 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.common.data.query.AlarmDataPageLink; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.dao.Dao; import java.util.Collection; +import java.util.Set; import java.util.UUID; /** @@ -48,4 +50,6 @@ public interface AlarmDao extends Dao { PageData findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection orderedEntityIds); + + Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 2bcef7cc88..9dff668f2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -39,9 +39,9 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -59,7 +59,6 @@ import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -306,7 +305,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ )); } return Futures.transform(Futures.successfulAsList(alarmFutures), - alarmInfos -> new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), + alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext()), MoreExecutors.directExecutor()); } return Futures.immediateFuture(alarms); @@ -315,37 +314,16 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { - TimePageLink nextPageLink = new TimePageLink(100); - boolean hasNext = true; - AlarmSeverity highestSeverity = null; - AlarmQuery query; - while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { - query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false, null); - PageData alarms = alarmDao.findAlarms(tenantId, query); - if (alarms.hasNext()) { - nextPageLink = nextPageLink.nextPageLink(); - } - AlarmSeverity severity = detectHighestSeverity(alarms.getData()); - if (severity == null) { - continue; - } - if (severity == AlarmSeverity.CRITICAL || highestSeverity == null) { - highestSeverity = severity; - } else { - highestSeverity = highestSeverity.compareTo(severity) < 0 ? highestSeverity : severity; - } + Set statusList = null; + if (alarmSearchStatus != null) { + statusList = alarmSearchStatus.getStatuses(); + } else if (alarmStatus != null) { + statusList = Collections.singleton(alarmStatus); } - return highestSeverity; - } - private AlarmSeverity detectHighestSeverity(List alarms) { - if (!alarms.isEmpty()) { - List sorted = new ArrayList(alarms); - sorted.sort(Comparator.comparing(Alarm::getSeverity)); - return sorted.get(0).getSeverity(); - } else { - return null; - } + Set alarmSeverities = alarmDao.findAlarmSeverities(tenantId, entityId, statusList); + + return alarmSeverities.stream().min(AlarmSeverity::compareTo).orElse(null); } private Alarm merge(Alarm existing, Alarm alarm) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 37382f3f9b..0189f32d0e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -190,6 +190,7 @@ public class AuditLogServiceImpl implements AuditLogService { case ATTRIBUTES_UPDATED: actionData.put("entityId", entityId.toString()); String scope = extractParameter(String.class, 0, additionalInfo); + @SuppressWarnings("unchecked") List attributes = extractParameter(List.class, 1, additionalInfo); actionData.put("scope", scope); ObjectNode attrsNode = JacksonUtil.newObjectNode(); @@ -205,6 +206,7 @@ public class AuditLogServiceImpl implements AuditLogService { actionData.put("entityId", entityId.toString()); scope = extractParameter(String.class, 0, additionalInfo); actionData.put("scope", scope); + @SuppressWarnings("unchecked") List keys = extractParameter(List.class, 1, additionalInfo); ArrayNode attrsArrayNode = actionData.putArray("attributes"); if (keys != null) { @@ -267,6 +269,7 @@ public class AuditLogServiceImpl implements AuditLogService { break; case TIMESERIES_UPDATED: actionData.put("entityId", entityId.toString()); + @SuppressWarnings("unchecked") List updatedTimeseries = extractParameter(List.class, 0, additionalInfo); if (updatedTimeseries != null) { ArrayNode result = actionData.putArray("timeseries"); @@ -283,6 +286,7 @@ public class AuditLogServiceImpl implements AuditLogService { break; case TIMESERIES_DELETED: actionData.put("entityId", entityId.toString()); + @SuppressWarnings("unchecked") List timeseriesKeys = extractParameter(List.class, 0, additionalInfo); if (timeseriesKeys != null) { ArrayNode timeseriesArrayNode = actionData.putArray("timeseries"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java index e19e580b9e..46190464db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java @@ -36,22 +36,22 @@ public class DummyAuditLogServiceImpl implements AuditLogService { @Override public PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List actionTypes, TimePageLink pageLink) { - return new PageData(); + return new PageData<>(); } @Override public PageData findAuditLogsByTenantIdAndUserId(TenantId tenantId, UserId userId, List actionTypes, TimePageLink pageLink) { - return new PageData(); + return new PageData<>(); } @Override public PageData findAuditLogsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId, List actionTypes, TimePageLink pageLink) { - return new PageData(); + return new PageData<>(); } @Override public PageData findAuditLogsByTenantId(TenantId tenantId, List actionTypes, TimePageLink pageLink) { - return new PageData(); + return new PageData<>(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index ec39ed8e0a..22ff7e6194 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -235,6 +235,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { + // remove device from cache in case null value cached in the distributed redis. + removeDeviceFromCache(device.getTenantId(), device.getName()); throw new DataValidationException("Device with such name already exists!"); } else { throw t; @@ -305,13 +307,17 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } deleteEntityRelations(tenantId, deviceId); + removeDeviceFromCache(tenantId, device.getName()); + + deviceDao.removeById(tenantId, deviceId.getId()); + } + + private void removeDeviceFromCache(TenantId tenantId, String name) { List list = new ArrayList<>(); - list.add(device.getTenantId()); - list.add(device.getName()); + list.add(tenantId); + list.add(name); Cache cache = cacheManager.getCache(DEVICE_CACHE); cache.evict(list); - - deviceDao.removeById(tenantId, deviceId.getId()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index b86ce814af..304b04a33d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -281,6 +281,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti tenantIdAndEntityId.add(entityId); Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); + @SuppressWarnings("unchecked") List fromCache = cache.get(tenantIdAndEntityId, List.class); if (fromCache != null) { return Futures.immediateFuture(fromCache); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 1639074338..7361dc21d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -53,7 +53,7 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep .userNameAttributeName(localClientRegistration.getUserNameAttributeName()) .jwkSetUri(localClientRegistration.getJwkSetUri()) .clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod())) - .redirectUriTemplate(defaultRedirectUriTemplate) + .redirectUri(defaultRedirectUriTemplate) .build(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 9b936b94e8..1fa9f057b2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -301,6 +301,7 @@ public class BaseRelationService implements RelationService { fromAndTypeGroup.add(EntitySearchDirection.FROM.name()); Cache cache = cacheManager.getCache(RELATIONS_CACHE); + @SuppressWarnings("unchecked") List fromCache = cache.get(fromAndTypeGroup, List.class); if (fromCache != null) { return Futures.immediateFuture(fromCache); @@ -382,6 +383,7 @@ public class BaseRelationService implements RelationService { toAndTypeGroup.add(EntitySearchDirection.TO.name()); Cache cache = cacheManager.getCache(RELATIONS_CACHE); + @SuppressWarnings("unchecked") List fromCache = cache.get(toAndTypeGroup, List.class); if (fromCache != null) { return Futures.immediateFuture(fromCache); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index f3206f30de..f34048c5ad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmInfoEntity; @@ -75,4 +76,12 @@ public interface AlarmRepository extends CrudRepository { @Param("searchText") String searchText, Pageable pageable); + @Query("SELECT alarm.severity FROM AlarmEntity alarm" + + " WHERE alarm.tenantId = :tenantId" + + " AND alarm.originatorId = :entityId" + + " AND ((:status) IS NULL OR alarm.status in (:status))") + Set findAlarmSeverities(@Param("tenantId") UUID tenantId, + @Param("entityId") UUID entityId, + @Param("status") Set status); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index d74092ac95..722928e1b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -24,6 +24,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -120,4 +121,9 @@ public class JpaAlarmDao extends JpaAbstractDao implements A public PageData findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection orderedEntityIds) { return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds); } + + @Override + public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status) { + return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), status); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 6b735125ef..4c34f6148a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -45,12 +45,12 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao getEntityClass() { return DashboardInfoEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected CrudRepository getCrudRepository() { return dashboardInfoRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 70136aa216..92085d44f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.device; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java index 230c23b604..785ba69c7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityDataAdapter.java @@ -83,7 +83,7 @@ public class EntityDataAdapter { if (value != null) { String strVal = value.toString(); // check number - if (strVal.length() > 0 && NumberUtils.isParsable(strVal)) { + if (NumberUtils.isNumber(strVal)) { try { long longVal = Long.parseLong(strVal); return Long.toString(longVal); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java index a27b39ced0..c5a1774ae6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java @@ -49,7 +49,7 @@ public interface RelationRepository String fromType); @Transactional - RelationEntity save(RelationEntity entity); + S save(S entity); @Transactional void deleteById(RelationCompositeKey id); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index f4678adcc9..760d9e7aeb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -52,12 +52,12 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao getEntityClass() { return RuleChainEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected CrudRepository getCrudRepository() { return ruleChainRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java index b99d26ebe0..362299f9b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java @@ -24,6 +24,8 @@ import org.thingsboard.server.dao.model.sql.RuleNodeEntity; import org.thingsboard.server.dao.rule.RuleNodeDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import java.util.UUID; + @Slf4j @Component public class JpaRuleNodeDao extends JpaAbstractSearchTextDao implements RuleNodeDao { @@ -32,12 +34,12 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao getEntityClass() { return RuleNodeEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected CrudRepository getCrudRepository() { return ruleNodeRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java index 3f1647b155..2be895bf86 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java @@ -39,12 +39,12 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao getEntityClass() { return RuleNodeStateEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected CrudRepository getCrudRepository() { return ruleNodeStateRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java index 4bf2f48a85..09e4bb55ae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql.rule; import org.springframework.data.repository.CrudRepository; import org.thingsboard.server.dao.model.sql.RuleNodeEntity; -public interface RuleNodeRepository extends CrudRepository { +import java.util.UUID; + +public interface RuleNodeRepository extends CrudRepository { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index e7b951f16d..2f817634c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -32,6 +32,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -108,7 +109,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD private PreparedStatement deletePartitionStmt; private boolean isInstall() { - return environment.acceptsProfiles("install"); + return environment.acceptsProfiles(Profiles.of("install")); } @PostConstruct @@ -116,16 +117,16 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD super.startExecutor(); if (!isInstall()) { getFetchStmt(Aggregation.NONE, DESC_ORDER); - Optional partition = NoSqlTsPartitionDate.parse(partitioning); - if (partition.isPresent()) { - tsFormat = partition.get(); - if (!isFixedPartitioning() && partitionsCacheSize > 0) { - cassandraTsPartitionsCache = new CassandraTsPartitionsCache(partitionsCacheSize); - } - } else { - log.warn("Incorrect configuration of partitioning {}", partitioning); - throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); + } + Optional partition = NoSqlTsPartitionDate.parse(partitioning); + if (partition.isPresent()) { + tsFormat = partition.get(); + if (!isFixedPartitioning() && partitionsCacheSize > 0) { + cassandraTsPartitionsCache = new CassandraTsPartitionsCache(partitionsCacheSize); } + } else { + log.warn("Incorrect configuration of partitioning {}", partitioning); + throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 519092274b..4efff2582e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.user; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -48,6 +48,7 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.HashMap; import java.util.Map; @@ -71,8 +72,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; - private static final ObjectMapper objectMapper = new ObjectMapper(); - @Value("${security.user_login_case_sensitive:true}") private boolean userLoginCaseSensitive; @@ -279,7 +278,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic User user = findUserById(tenantId, userId); JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { - additionalInfo = objectMapper.createObjectNode(); + additionalInfo = JacksonUtil.newObjectNode(); } ((ObjectNode) additionalInfo).put(USER_CREDENTIALS_ENABLED, enabled); user.setAdditionalInfo(additionalInfo); @@ -302,7 +301,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private void setLastLoginTs(User user) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { - additionalInfo = objectMapper.createObjectNode(); + additionalInfo = JacksonUtil.newObjectNode(); } ((ObjectNode) additionalInfo).put(LAST_LOGIN_TS, System.currentTimeMillis()); user.setAdditionalInfo(additionalInfo); @@ -311,7 +310,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private void resetFailedLoginAttempts(User user) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { - additionalInfo = objectMapper.createObjectNode(); + additionalInfo = JacksonUtil.newObjectNode(); } ((ObjectNode) additionalInfo).put(FAILED_LOGIN_ATTEMPTS, 0); user.setAdditionalInfo(additionalInfo); @@ -329,7 +328,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private int increaseFailedLoginAttempts(User user) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { - additionalInfo = objectMapper.createObjectNode(); + additionalInfo = JacksonUtil.newObjectNode(); } int failedLoginAttempts = 0; if (additionalInfo.has(FAILED_LOGIN_ATTEMPTS)) { @@ -353,26 +352,30 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private void updatePasswordHistory(User user, UserCredentials userCredentials) { JsonNode additionalInfo = user.getAdditionalInfo(); if (!(additionalInfo instanceof ObjectNode)) { - additionalInfo = objectMapper.createObjectNode(); + additionalInfo = JacksonUtil.newObjectNode(); } + Map userPasswordHistoryMap = null; + JsonNode userPasswordHistoryJson; if (additionalInfo.has(USER_PASSWORD_HISTORY)) { - JsonNode userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY); - Map userPasswordHistoryMap = objectMapper.convertValue(userPasswordHistoryJson, Map.class); + userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY); + userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>(){}); + } + if (userPasswordHistoryMap != null) { userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword()); - userPasswordHistoryJson = objectMapper.valueToTree(userPasswordHistoryMap); + userPasswordHistoryJson = JacksonUtil.valueToTree(userPasswordHistoryMap); ((ObjectNode) additionalInfo).replace(USER_PASSWORD_HISTORY, userPasswordHistoryJson); } else { - Map userPasswordHistoryMap = new HashMap<>(); + userPasswordHistoryMap = new HashMap<>(); userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword()); - JsonNode userPasswordHistoryJson = objectMapper.valueToTree(userPasswordHistoryMap); + userPasswordHistoryJson = JacksonUtil.valueToTree(userPasswordHistoryMap); ((ObjectNode) additionalInfo).set(USER_PASSWORD_HISTORY, userPasswordHistoryJson); } user.setAdditionalInfo(additionalInfo); saveUser(user); } - private DataValidator userValidator = - new DataValidator() { + private final DataValidator userValidator = + new DataValidator<>() { @Override protected void validateCreate(TenantId tenantId, User user) { if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { @@ -452,8 +455,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } }; - private DataValidator userCredentialsValidator = - new DataValidator() { + private final DataValidator userCredentialsValidator = + new DataValidator<>() { @Override protected void validateCreate(TenantId tenantId, UserCredentials userCredentials) { @@ -484,7 +487,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } }; - private PaginatedRemover tenantAdminsRemover = new PaginatedRemover() { + private final PaginatedRemover tenantAdminsRemover = new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return userDao.findTenantAdmins(id.getId(), pageLink); @@ -496,7 +499,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } }; - private PaginatedRemover customerUsersRemover = new PaginatedRemover() { + private final PaginatedRemover customerUsersRemover = new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, CustomerId id, PageLink pageLink) { return userDao.findCustomerUsers(tenantId.getId(), id.getId(), pageLink); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 27a60ddead..40867e872f 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS customer ( CREATE TABLE IF NOT EXISTS dashboard ( id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY, created_time bigint NOT NULL, - configuration varchar, + configuration varchar(10000000), assigned_customers varchar(1000000), search_text varchar(255), tenant_id uuid, diff --git a/dao/src/test/java/org/apache/cassandra/io/sstable/Descriptor.java b/dao/src/test/java/org/apache/cassandra/io/sstable/Descriptor.java index a5e6122537..69a164f729 100644 --- a/dao/src/test/java/org/apache/cassandra/io/sstable/Descriptor.java +++ b/dao/src/test/java/org/apache/cassandra/io/sstable/Descriptor.java @@ -244,6 +244,7 @@ public class Descriptor * * @return A Descriptor for the SSTable, and the Component remainder. */ + @SuppressWarnings("deprecation") public static Pair fromFilename(File directory, String name, boolean skipComponent) { File parentDirectory = directory != null ? directory : new File("."); @@ -319,6 +320,7 @@ public class Descriptor component); } + @SuppressWarnings("deprecation") public IMetadataSerializer getMetadataSerializer() { if (version.hasNewStatsFile()) diff --git a/dao/src/test/java/org/apache/cassandra/io/sstable/format/SSTableFormat.java b/dao/src/test/java/org/apache/cassandra/io/sstable/format/SSTableFormat.java index af6af442a3..350d27591f 100644 --- a/dao/src/test/java/org/apache/cassandra/io/sstable/format/SSTableFormat.java +++ b/dao/src/test/java/org/apache/cassandra/io/sstable/format/SSTableFormat.java @@ -56,6 +56,7 @@ public interface SSTableFormat return BIG; } + @SuppressWarnings("deprecation") private Type(String name, SSTableFormat info) { //Since format comes right after generation diff --git a/dao/src/test/java/org/apache/cassandra/io/util/FileUtils.java b/dao/src/test/java/org/apache/cassandra/io/util/FileUtils.java new file mode 100644 index 0000000000..2609a2c3d0 --- /dev/null +++ b/dao/src/test/java/org/apache/cassandra/io/util/FileUtils.java @@ -0,0 +1,760 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cassandra.io.util; + +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.StreamSupport; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.cassandra.concurrent.ScheduledExecutors; +import org.apache.cassandra.io.FSError; +import org.apache.cassandra.io.FSErrorHandler; +import org.apache.cassandra.io.FSReadError; +import org.apache.cassandra.io.FSWriteError; +import org.apache.cassandra.io.sstable.CorruptSSTableException; +import org.apache.cassandra.utils.JVMStabilityInspector; + +import static com.google.common.base.Throwables.throwIfUnchecked; +import static org.apache.cassandra.utils.Throwables.maybeFail; +import static org.apache.cassandra.utils.Throwables.merge; + +public final class FileUtils +{ + public static final Charset CHARSET = StandardCharsets.UTF_8; + + private static final Logger logger = LoggerFactory.getLogger(FileUtils.class); + public static final long ONE_KB = 1024; + public static final long ONE_MB = 1024 * ONE_KB; + public static final long ONE_GB = 1024 * ONE_MB; + public static final long ONE_TB = 1024 * ONE_GB; + + private static final DecimalFormat df = new DecimalFormat("#.##"); + public static final boolean isCleanerAvailable = false; + private static final AtomicReference> fsErrorHandler = new AtomicReference<>(Optional.empty()); + + public static void createHardLink(String from, String to) + { + createHardLink(new File(from), new File(to)); + } + + public static void createHardLink(File from, File to) + { + if (to.exists()) + throw new RuntimeException("Tried to create duplicate hard link to " + to); + if (!from.exists()) + throw new RuntimeException("Tried to hard link to file that does not exist " + from); + + try + { + Files.createLink(to.toPath(), from.toPath()); + } + catch (IOException e) + { + throw new FSWriteError(e, to); + } + } + + public static File createTempFile(String prefix, String suffix, File directory) + { + try + { + return File.createTempFile(prefix, suffix, directory); + } + catch (IOException e) + { + throw new FSWriteError(e, directory); + } + } + + public static File createTempFile(String prefix, String suffix) + { + return createTempFile(prefix, suffix, new File(System.getProperty("java.io.tmpdir"))); + } + + public static Throwable deleteWithConfirm(String filePath, boolean expect, Throwable accumulate) + { + return deleteWithConfirm(new File(filePath), expect, accumulate); + } + + public static Throwable deleteWithConfirm(File file, boolean expect, Throwable accumulate) + { + boolean exists = file.exists(); + assert exists || !expect : "attempted to delete non-existing file " + file.getName(); + try + { + if (exists) + Files.delete(file.toPath()); + } + catch (Throwable t) + { + try + { + throw new FSWriteError(t, file); + } + catch (Throwable t2) + { + accumulate = merge(accumulate, t2); + } + } + return accumulate; + } + + public static void deleteWithConfirm(String file) + { + deleteWithConfirm(new File(file)); + } + + public static void deleteWithConfirm(File file) + { + maybeFail(deleteWithConfirm(file, true, null)); + } + + public static void renameWithOutConfirm(String from, String to) + { + try + { + atomicMoveWithFallback(new File(from).toPath(), new File(to).toPath()); + } + catch (IOException e) + { + if (logger.isTraceEnabled()) + logger.trace("Could not move file "+from+" to "+to, e); + } + } + + public static void renameWithConfirm(String from, String to) + { + renameWithConfirm(new File(from), new File(to)); + } + + public static void renameWithConfirm(File from, File to) + { + assert from.exists(); + if (logger.isTraceEnabled()) + logger.trace("Renaming {} to {}", from.getPath(), to.getPath()); + // this is not FSWE because usually when we see it it's because we didn't close the file before renaming it, + // and Windows is picky about that. + try + { + atomicMoveWithFallback(from.toPath(), to.toPath()); + } + catch (IOException e) + { + throw new RuntimeException(String.format("Failed to rename %s to %s", from.getPath(), to.getPath()), e); + } + } + + /** + * Move a file atomically, if it fails, it falls back to a non-atomic operation + * @param from + * @param to + * @throws IOException + */ + private static void atomicMoveWithFallback(Path from, Path to) throws IOException + { + try + { + Files.move(from, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + } + catch (AtomicMoveNotSupportedException e) + { + logger.trace("Could not do an atomic move", e); + Files.move(from, to, StandardCopyOption.REPLACE_EXISTING); + } + + } + public static void truncate(String path, long size) + { + try(FileChannel channel = FileChannel.open(Paths.get(path), StandardOpenOption.READ, StandardOpenOption.WRITE)) + { + channel.truncate(size); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + + public static void closeQuietly(Closeable c) + { + try + { + if (c != null) + c.close(); + } + catch (Exception e) + { + logger.warn("Failed closing {}", c, e); + } + } + + public static void closeQuietly(AutoCloseable c) + { + try + { + if (c != null) + c.close(); + } + catch (Exception e) + { + logger.warn("Failed closing {}", c, e); + } + } + + public static void close(Closeable... cs) throws IOException + { + close(Arrays.asList(cs)); + } + + public static void close(Iterable cs) throws IOException + { + Throwable e = null; + for (Closeable c : cs) + { + try + { + if (c != null) + c.close(); + } + catch (Throwable ex) + { + if (e == null) e = ex; + else e.addSuppressed(ex); + logger.warn("Failed closing stream {}", c, ex); + } + } + maybeFail(e, IOException.class); + } + + public static void closeQuietly(Iterable cs) + { + for (AutoCloseable c : cs) + { + try + { + if (c != null) + c.close(); + } + catch (Exception ex) + { + logger.warn("Failed closing {}", c, ex); + } + } + } + + public static String getCanonicalPath(String filename) + { + try + { + return new File(filename).getCanonicalPath(); + } + catch (IOException e) + { + throw new FSReadError(e, filename); + } + } + + public static String getCanonicalPath(File file) + { + try + { + return file.getCanonicalPath(); + } + catch (IOException e) + { + throw new FSReadError(e, file); + } + } + + /** Return true if file is contained in folder */ + public static boolean isContained(File folder, File file) + { + Path folderPath = Paths.get(getCanonicalPath(folder)); + Path filePath = Paths.get(getCanonicalPath(file)); + + return filePath.startsWith(folderPath); + } + + /** Convert absolute path into a path relative to the base path */ + public static String getRelativePath(String basePath, String path) + { + try + { + return Paths.get(basePath).relativize(Paths.get(path)).toString(); + } + catch(Exception ex) + { + String absDataPath = FileUtils.getCanonicalPath(basePath); + return Paths.get(absDataPath).relativize(Paths.get(path)).toString(); + } + } + + public static void clean(ByteBuffer buffer) + { + if (buffer == null) + return; + } + + public static void createDirectory(String directory) + { + createDirectory(new File(directory)); + } + + public static void createDirectory(File directory) + { + if (!directory.exists()) + { + if (!directory.mkdirs()) + throw new FSWriteError(new IOException("Failed to mkdirs " + directory), directory); + } + } + + public static boolean delete(String file) + { + File f = new File(file); + return f.delete(); + } + + public static void delete(File... files) + { + if (files == null) + { + // CASSANDRA-13389: some callers use Files.listFiles() which, on error, silently returns null + logger.debug("Received null list of files to delete"); + return; + } + + for ( File file : files ) + { + file.delete(); + } + } + + public static void deleteAsync(final String file) + { + Runnable runnable = new Runnable() + { + public void run() + { + deleteWithConfirm(new File(file)); + } + }; + ScheduledExecutors.nonPeriodicTasks.execute(runnable); + } + + public static void visitDirectory(Path dir, Predicate filter, Consumer consumer) + { + try (DirectoryStream stream = Files.newDirectoryStream(dir)) + { + StreamSupport.stream(stream.spliterator(), false) + .map(Path::toFile) + // stream directories are weakly consistent so we always check if the file still exists + .filter(f -> f.exists() && (filter == null || filter.test(f))) + .forEach(consumer); + } + catch (IOException|DirectoryIteratorException ex) + { + logger.error("Failed to list files in {} with exception: {}", dir, ex.getMessage(), ex); + } + } + + public static String stringifyFileSize(double value) + { + double d; + if ( value >= ONE_TB ) + { + d = value / ONE_TB; + String val = df.format(d); + return val + " TiB"; + } + else if ( value >= ONE_GB ) + { + d = value / ONE_GB; + String val = df.format(d); + return val + " GiB"; + } + else if ( value >= ONE_MB ) + { + d = value / ONE_MB; + String val = df.format(d); + return val + " MiB"; + } + else if ( value >= ONE_KB ) + { + d = value / ONE_KB; + String val = df.format(d); + return val + " KiB"; + } + else + { + String val = df.format(value); + return val + " bytes"; + } + } + + /** + * Deletes all files and subdirectories under "dir". + * @param dir Directory to be deleted + * @throws FSWriteError if any part of the tree cannot be deleted + */ + public static void deleteRecursive(File dir) + { + if (dir.isDirectory()) + { + String[] children = dir.list(); + for (String child : children) + deleteRecursive(new File(dir, child)); + } + + // The directory is now empty so now it can be smoked + deleteWithConfirm(dir); + } + + /** + * Schedules deletion of all file and subdirectories under "dir" on JVM shutdown. + * @param dir Directory to be deleted + */ + public static void deleteRecursiveOnExit(File dir) + { + if (dir.isDirectory()) + { + String[] children = dir.list(); + for (String child : children) + deleteRecursiveOnExit(new File(dir, child)); + } + + logger.trace("Scheduling deferred deletion of file: {}", dir); + dir.deleteOnExit(); + } + + public static void handleCorruptSSTable(CorruptSSTableException e) + { + fsErrorHandler.get().ifPresent(handler -> handler.handleCorruptSSTable(e)); + } + + public static void handleFSError(FSError e) + { + fsErrorHandler.get().ifPresent(handler -> handler.handleFSError(e)); + } + + /** + * handleFSErrorAndPropagate will invoke the disk failure policy error handler, + * which may or may not stop the daemon or transports. However, if we don't exit, + * we still want to propagate the exception to the caller in case they have custom + * exception handling + * + * @param e A filesystem error + */ + public static void handleFSErrorAndPropagate(FSError e) + { + JVMStabilityInspector.inspectThrowable(e); + throwIfUnchecked(e); + throw new RuntimeException(e); + } + + /** + * Get the size of a directory in bytes + * @param folder The directory for which we need size. + * @return The size of the directory + */ + public static long folderSize(File folder) + { + final long [] sizeArr = {0L}; + try + { + Files.walkFileTree(folder.toPath(), new SimpleFileVisitor() + { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + { + sizeArr[0] += attrs.size(); + return FileVisitResult.CONTINUE; + } + }); + } + catch (IOException e) + { + logger.error("Error while getting {} folder size. {}", folder, e); + } + return sizeArr[0]; + } + + public static void copyTo(DataInput in, OutputStream out, int length) throws IOException + { + byte[] buffer = new byte[64 * 1024]; + int copiedBytes = 0; + + while (copiedBytes + buffer.length < length) + { + in.readFully(buffer); + out.write(buffer); + copiedBytes += buffer.length; + } + + if (copiedBytes < length) + { + int left = length - copiedBytes; + in.readFully(buffer, 0, left); + out.write(buffer, 0, left); + } + } + + public static boolean isSubDirectory(File parent, File child) throws IOException + { + parent = parent.getCanonicalFile(); + child = child.getCanonicalFile(); + + File toCheck = child; + while (toCheck != null) + { + if (parent.equals(toCheck)) + return true; + toCheck = toCheck.getParentFile(); + } + return false; + } + + public static void append(File file, String ... lines) + { + if (file.exists()) + write(file, Arrays.asList(lines), StandardOpenOption.APPEND); + else + write(file, Arrays.asList(lines), StandardOpenOption.CREATE); + } + + public static void appendAndSync(File file, String ... lines) + { + if (file.exists()) + write(file, Arrays.asList(lines), StandardOpenOption.APPEND, StandardOpenOption.SYNC); + else + write(file, Arrays.asList(lines), StandardOpenOption.CREATE, StandardOpenOption.SYNC); + } + + public static void replace(File file, String ... lines) + { + write(file, Arrays.asList(lines), StandardOpenOption.TRUNCATE_EXISTING); + } + + public static void write(File file, List lines, StandardOpenOption ... options) + { + try + { + Files.write(file.toPath(), + lines, + CHARSET, + options); + } + catch (IOException ex) + { + throw new RuntimeException(ex); + } + } + + public static List readLines(File file) + { + try + { + return Files.readAllLines(file.toPath(), CHARSET); + } + catch (IOException ex) + { + if (ex instanceof NoSuchFileException) + return Collections.emptyList(); + + throw new RuntimeException(ex); + } + } + + public static void setFSErrorHandler(FSErrorHandler handler) + { + fsErrorHandler.getAndSet(Optional.ofNullable(handler)); + } + + /** + * Returns the size of the specified partition. + *

This method handles large file system by returning {@code Long.MAX_VALUE} if the size overflow. + * See JDK-8179320 for more information.

+ * + * @param file the partition + * @return the size, in bytes, of the partition or {@code 0L} if the abstract pathname does not name a partition + */ + public static long getTotalSpace(File file) + { + return handleLargeFileSystem(file.getTotalSpace()); + } + + /** + * Returns the number of unallocated bytes on the specified partition. + *

This method handles large file system by returning {@code Long.MAX_VALUE} if the number of unallocated bytes + * overflow. See JDK-8179320 for more information

+ * + * @param file the partition + * @return the number of unallocated bytes on the partition or {@code 0L} + * if the abstract pathname does not name a partition. + */ + public static long getFreeSpace(File file) + { + return handleLargeFileSystem(file.getFreeSpace()); + } + + /** + * Returns the number of available bytes on the specified partition. + *

This method handles large file system by returning {@code Long.MAX_VALUE} if the number of available bytes + * overflow. See JDK-8179320 for more information

+ * + * @param file the partition + * @return the number of available bytes on the partition or {@code 0L} + * if the abstract pathname does not name a partition. + */ + public static long getUsableSpace(File file) + { + return handleLargeFileSystem(file.getUsableSpace()); + } + + /** + * Returns the {@link FileStore} representing the file store where a file + * is located. This {@link FileStore} handles large file system by returning {@code Long.MAX_VALUE} + * from {@code FileStore#getTotalSpace()}, {@code FileStore#getUnallocatedSpace()} and {@code FileStore#getUsableSpace()} + * it the value is bigger than {@code Long.MAX_VALUE}. See JDK-8162520 + * for more information. + * + * @param path the path to the file + * @return the file store where the file is stored + */ + public static FileStore getFileStore(Path path) throws IOException + { + return new SafeFileStore(Files.getFileStore(path)); + } + + /** + * Handle large file system by returning {@code Long.MAX_VALUE} when the size overflows. + * @param size returned by the Java's FileStore methods + * @return the size or {@code Long.MAX_VALUE} if the size was bigger than {@code Long.MAX_VALUE} + */ + private static long handleLargeFileSystem(long size) + { + return size < 0 ? Long.MAX_VALUE : size; + } + + /** + * Private constructor as the class contains only static methods. + */ + private FileUtils() + { + } + + /** + * FileStore decorator used to safely handle large file system. + * + *

Java's FileStore methods (getTotalSpace/getUnallocatedSpace/getUsableSpace) are limited to reporting bytes as + * signed long (2^63-1), if the filesystem is any bigger, then the size overflows. {@code SafeFileStore} will + * return {@code Long.MAX_VALUE} if the size overflow.

+ * + * @see https://bugs.openjdk.java.net/browse/JDK-8162520. + */ + private static final class SafeFileStore extends FileStore + { + /** + * The decorated {@code FileStore} + */ + private final FileStore fileStore; + + public SafeFileStore(FileStore fileStore) + { + this.fileStore = fileStore; + } + + @Override + public String name() + { + return fileStore.name(); + } + + @Override + public String type() + { + return fileStore.type(); + } + + @Override + public boolean isReadOnly() + { + return fileStore.isReadOnly(); + } + + @Override + public long getTotalSpace() throws IOException + { + return handleLargeFileSystem(fileStore.getTotalSpace()); + } + + @Override + public long getUsableSpace() throws IOException + { + return handleLargeFileSystem(fileStore.getUsableSpace()); + } + + @Override + public long getUnallocatedSpace() throws IOException + { + return handleLargeFileSystem(fileStore.getUnallocatedSpace()); + } + + @Override + public boolean supportsFileAttributeView(Class type) + { + return fileStore.supportsFileAttributeView(type); + } + + @Override + public boolean supportsFileAttributeView(String name) + { + return fileStore.supportsFileAttributeView(name); + } + + @Override + public V getFileStoreAttributeView(Class type) + { + return fileStore.getFileStoreAttributeView(type); + } + + @Override + public Object getAttribute(String attribute) throws IOException + { + return fileStore.getAttribute(attribute); + } + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java index 5530bdbcb2..d3c6c97367 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java @@ -25,7 +25,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Spy; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.springframework.core.env.Environment; import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.common.data.id.TenantId; @@ -35,9 +35,9 @@ import org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao; import java.util.UUID; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java index 3536b6066f..3fd416f28c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java @@ -347,13 +347,69 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest { } private AlarmDataQuery toQuery(AlarmDataPageLink pageLink){ - return toQuery(pageLink, Collections.EMPTY_LIST); + return toQuery(pageLink, Collections.emptyList()); } private AlarmDataQuery toQuery(AlarmDataPageLink pageLink, List alarmFields){ return new AlarmDataQuery(new DeviceTypeFilter(), pageLink, null, null, null, alarmFields); } + @Test + public void testFindHighestAlarmSeverity() throws ExecutionException, InterruptedException { + Customer customer = new Customer(); + customer.setTitle("TestCustomer"); + customer.setTenantId(tenantId); + customer = customerService.saveCustomer(customer); + + Device customerDevice = new Device(); + customerDevice.setName("TestCustomerDevice"); + customerDevice.setType("default"); + customerDevice.setTenantId(tenantId); + customerDevice.setCustomerId(customer.getId()); + customerDevice = deviceService.saveDevice(customerDevice); + + // no one alarms was created + Assert.assertNull(alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); + + Alarm alarm1 = Alarm.builder() + .tenantId(tenantId) + .originator(customerDevice.getId()) + .type(TEST_ALARM) + .severity(AlarmSeverity.MAJOR) + .status(AlarmStatus.ACTIVE_UNACK) + .startTs(System.currentTimeMillis()) + .build(); + alarm1 = alarmService.createOrUpdateAlarm(alarm1).getAlarm(); + alarmService.clearAlarm(tenantId, alarm1.getId(), null, System.currentTimeMillis()).get(); + + Alarm alarm2 = Alarm.builder() + .tenantId(tenantId) + .originator(customerDevice.getId()) + .type(TEST_ALARM) + .severity(AlarmSeverity.MINOR) + .status(AlarmStatus.ACTIVE_ACK) + .startTs(System.currentTimeMillis()) + .build(); + alarm2 = alarmService.createOrUpdateAlarm(alarm2).getAlarm(); + alarmService.clearAlarm(tenantId, alarm2.getId(), null, System.currentTimeMillis()).get(); + + Alarm alarm3 = Alarm.builder() + .tenantId(tenantId) + .originator(customerDevice.getId()) + .type(TEST_ALARM) + .severity(AlarmSeverity.CRITICAL) + .status(AlarmStatus.ACTIVE_ACK) + .startTs(System.currentTimeMillis()) + .build(); + alarm3 = alarmService.createOrUpdateAlarm(alarm3).getAlarm(); + + Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.UNACK, null)); + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, null)); + Assert.assertEquals(AlarmSeverity.MAJOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_UNACK)); + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), AlarmSearchStatus.ACTIVE, null)); + Assert.assertEquals(AlarmSeverity.MINOR, alarmService.findHighestAlarmSeverity(tenantId, customerDevice.getId(), null, AlarmStatus.CLEARED_ACK)); + } + @Test public void testFindAlarmUsingAlarmDataQuery() throws ExecutionException, InterruptedException { AssetId parentId = new AssetId(Uuids.timeBased()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index e2abf30534..22e5ecc4b0 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -911,10 +911,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { .getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()); } List deviceTemperatures = temperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList()); - if (DaoTestUtil.getSqlDbType(template) == SqlDbType.H2) { - // in H2 double values are stored with E0 in the end of the string - loadedTemperatures = loadedTemperatures.stream().map(s -> s.substring(0, s.length() - 2)).collect(Collectors.toList()); - } + Assert.assertEquals(deviceTemperatures, loadedTemperatures); pageLink = new EntityDataPageLink(10, 0, null, sortOrder); @@ -942,10 +939,6 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { entityData.getLatest().get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).collect(Collectors.toList()); List deviceHighTemperatures = highTemperatures.stream().map(aDouble -> Double.toString(aDouble)).collect(Collectors.toList()); - if (DaoTestUtil.getSqlDbType(template) == SqlDbType.H2) { - // in H2 double values are stored with E0 in the end of the string - loadedHighTemperatures = loadedHighTemperatures.stream().map(s -> s.substring(0, s.length() - 2)).collect(Collectors.toList()); - } Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures); deviceService.deleteDevicesByTenantId(tenantId); @@ -1284,7 +1277,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { EntityDataPageLink pageLink = new EntityDataPageLink(100, 0, null, sortOrder); EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, null, deviceTypeFilters); - PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query); + PageData data = entityService.findEntityDataByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), query); List loadedEntities = getLoadedEntities(data, query); Assert.assertEquals(devices.size(), loadedEntities.size()); @@ -1311,7 +1304,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { return A.containsAll(B) && B.containsAll(A); } - private List getLoadedEntities(PageData data, EntityDataQuery query) { + private List getLoadedEntities(PageData data, EntityDataQuery query) { List loadedEntities = new ArrayList<>(data.getData()); while (data.hasNext()) { diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index eefa4f0b95..6fe2dc2112 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -6,6 +6,8 @@ sql.ts_inserts_fixed_thread_pool_size=10 spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.properties.hibernate.order_by.default_null_ordering=last +spring.jpa.properties.hibernate.jdbc.log.warnings=false + spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=none spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index 058d7ac056..358b9ea3af 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -7,6 +7,8 @@ sql.ts_key_value_partitioning=MONTHS # spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true spring.jpa.properties.hibernate.order_by.default_null_ordering=last +spring.jpa.properties.hibernate.jdbc.log.warnings=false + spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=validate spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect @@ -49,4 +51,4 @@ queue.rule-engine.queues[0].pack-processing-timeout=3000 queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES queue.rule-engine.queues[0].submit-strategy.type=BURST -sql.log_entity_queries=true \ No newline at end of file +sql.log_entity_queries=true diff --git a/docker/tb-node/conf/thingsboard.conf b/docker/tb-node/conf/thingsboard.conf index 3f27bf2859..ead283da70 100644 --- a/docker/tb-node/conf/thingsboard.conf +++ b/docker/tb-node/conf/thingsboard.conf @@ -15,10 +15,10 @@ # export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/thingsboard/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=thingsboard.out export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions diff --git a/docker/tb-transports/coap/conf/tb-coap-transport.conf b/docker/tb-transports/coap/conf/tb-coap-transport.conf index 7c5f134183..2db8f579fc 100644 --- a/docker/tb-transports/coap/conf/tb-coap-transport.conf +++ b/docker/tb-transports/coap/conf/tb-coap-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-coap-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-coap-transport.out export LOADER_PATH=/usr/share/tb-coap-transport/conf diff --git a/docker/tb-transports/http/conf/tb-http-transport.conf b/docker/tb-transports/http/conf/tb-http-transport.conf index 795f5424db..b61070a3ff 100644 --- a/docker/tb-transports/http/conf/tb-http-transport.conf +++ b/docker/tb-transports/http/conf/tb-http-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-http-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-http-transport.out export LOADER_PATH=/usr/share/tb-http-transport/conf diff --git a/docker/tb-transports/lwm2m/conf/tb-lwm2m-transport.conf b/docker/tb-transports/lwm2m/conf/tb-lwm2m-transport.conf index 22b1444668..2d28a77174 100644 --- a/docker/tb-transports/lwm2m/conf/tb-lwm2m-transport.conf +++ b/docker/tb-transports/lwm2m/conf/tb-lwm2m-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-lwm2m-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-lwm2m-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-lwm2m-transport/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-lwm2m-transport.out export LOADER_PATH=/usr/share/tb-lwm2m-transport/conf diff --git a/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf b/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf index cc23a7af65..5c0b66ba8e 100644 --- a/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf +++ b/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=tb-mqtt-transport.out export LOADER_PATH=/usr/share/tb-mqtt-transport/conf diff --git a/lombok.config b/lombok.config new file mode 100644 index 0000000000..d904701090 --- /dev/null +++ b/lombok.config @@ -0,0 +1,2 @@ +config.stopbubbling = true +lombok.anyconstructor.addconstructorproperties = true diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index e17e5d0261..412cf3094b 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.msa.mapper.WsTelemetryResponse; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; @@ -114,14 +115,17 @@ public abstract class AbstractContainerTest { } protected Device createDevice(String name) { - return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); + Device device = new Device(); + device.setName(name + RandomStringUtils.randomAlphanumeric(7)); + device.setType("DEFAULT"); + return restClient.saveDevice(device); } protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); SSLContextBuilder builder = SSLContexts.custom(); builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); - wsClient.setSocket(builder.build().getSocketFactory().createSocket()); + wsClient.setSocketFactory(builder.build().getSocketFactory()); wsClient.connectBlocking(); JsonObject cmdsObject = new JsonObject(); @@ -218,24 +222,7 @@ public abstract class AbstractContainerTest { SSLContextBuilder builder = SSLContexts.custom(); builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); SSLContext sslContext = builder.build(); - SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() { - @Override - public void verify(String host, SSLSocket ssl) { - } - - @Override - public void verify(String host, X509Certificate cert) { - } - - @Override - public void verify(String host, String[] cns, String[] subjectAlts) { - } - - @Override - public boolean verify(String s, SSLSession sslSession) { - return true; - } - }); + SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, (s, sslSession) -> true); Registry socketFactoryRegistry = RegistryBuilder .create() diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 78e94d001c..acaf417882 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -34,7 +34,7 @@ import java.util.Map; @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) public class ContainerTestSuite { - private static DockerComposeContainer testContainer; + private static DockerComposeContainer testContainer; @ClassRule public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller(); @@ -43,7 +43,7 @@ public class ContainerTestSuite { public static DockerComposeContainer getTestContainer() { if (testContainer == null) { boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers")); - testContainer = new DockerComposeContainer( + testContainer = new DockerComposeContainer<>( new File("./../../docker/docker-compose.yml"), new File("./../../docker/docker-compose.postgres.yml"), new File("./../../docker/docker-compose.postgres.volumes.yml"), diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index 0d20661b25..855d2a926c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -44,7 +44,7 @@ public class HttpClientTest extends AbstractContainerTest { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("http_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() @@ -73,7 +73,7 @@ public class HttpClientTest extends AbstractContainerTest { TB_TOKEN = restClient.getToken(); Device device = createDevice("test"); - String accessToken = restClient.getCredentials(device.getId()).getCredentialsId(); + String accessToken = restClient.getDeviceCredentialsByDeviceId(device.getId()).get().getCredentialsId(); assertNotNull(accessToken); ResponseEntity deviceSharedAttributes = restClient.getRestTemplate() @@ -92,6 +92,7 @@ public class HttpClientTest extends AbstractContainerTest { TimeUnit.SECONDS.sleep(3); + @SuppressWarnings("deprecation") Optional allOptional = restClient.getAttributes(accessToken, null, null); assertTrue(allOptional.isPresent()); @@ -101,6 +102,7 @@ public class HttpClientTest extends AbstractContainerTest { assertEquals(mapper.readTree(createPayload().toString()), all.get("shared")); assertEquals(mapper.readTree(createPayload().toString()), all.get("client")); + @SuppressWarnings("deprecation") Optional sharedOptional = restClient.getAttributes(accessToken, null, "stringKey"); assertTrue(sharedOptional.isPresent()); @@ -108,6 +110,7 @@ public class HttpClientTest extends AbstractContainerTest { assertEquals(shared.get("shared").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString())); assertFalse(shared.has("client")); + @SuppressWarnings("deprecation") Optional clientOptional = restClient.getAttributes(accessToken, "longKey,stringKey", null); assertTrue(clientOptional.isPresent()); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index f3212a53cc..c8156b6ef1 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -62,7 +62,7 @@ public class MqttClientTest extends AbstractContainerTest { public void telemetryUpload() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); @@ -89,7 +89,7 @@ public class MqttClientTest extends AbstractContainerTest { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); @@ -113,7 +113,7 @@ public class MqttClientTest extends AbstractContainerTest { public void publishAttributeUpdateToServer() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); MqttMessageListener listener = new MqttMessageListener(); @@ -144,7 +144,7 @@ public class MqttClientTest extends AbstractContainerTest { public void requestAttributeValuesFromServer() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); MqttMessageListener listener = new MqttMessageListener(); @@ -204,7 +204,7 @@ public class MqttClientTest extends AbstractContainerTest { public void subscribeToAttributeUpdatesFromServer() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); @@ -250,7 +250,7 @@ public class MqttClientTest extends AbstractContainerTest { public void serverSideRpc() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); @@ -297,7 +297,7 @@ public class MqttClientTest extends AbstractContainerTest { public void clientSideRpc() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); diff --git a/msa/pom.xml b/msa/pom.xml index 4639cae78f..3d7bf3f347 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -50,7 +50,7 @@ com.spotify dockerfile-maven-plugin - 1.4.5 + 1.4.13 diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile index 523ba440f0..21babfdfbf 100644 --- a/msa/tb-node/docker/Dockerfile +++ b/msa/tb-node/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-node.sh ${pkg.name}.deb /tmp/ diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index f68266f4c5..f9eddd420c 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -14,14 +14,15 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 RUN apt-get update RUN apt-get install -y curl nmap procps -RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null +RUN echo 'deb http://ftp.us.debian.org/debian sid main' | tee --append /etc/apt/sources.list.d/debian.list > /dev/null +RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ sid-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - -RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null -RUN curl -L https://www.apache.org/dist/cassandra/KEYS | apt-key add - +RUN echo 'deb http://downloads.apache.org/cassandra/debian 40x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null +RUN curl -L https://downloads.apache.org/cassandra/KEYS | apt-key add - ENV PG_MAJOR=11 RUN apt-get update RUN apt-get install -y cassandra cassandra-tools postgresql-11 diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile index 3ce02c3297..9b314a5a5d 100644 --- a/msa/tb/docker-postgres/Dockerfile +++ b/msa/tb/docker-postgres/Dockerfile @@ -14,11 +14,12 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 RUN apt-get update RUN apt-get install -y curl -RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ stretch-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null +RUN echo 'deb http://ftp.us.debian.org/debian sid main' | tee --append /etc/apt/sources.list.d/debian.list > /dev/null +RUN echo 'deb http://apt.postgresql.org/pub/repos/apt/ sid-pgdg main' | tee --append /etc/apt/sources.list.d/pgdg.list > /dev/null RUN curl -L https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - ENV PG_MAJOR 11 RUN apt-get update diff --git a/msa/tb/docker-tb/Dockerfile b/msa/tb/docker-tb/Dockerfile index 93eee318dc..9920e6257c 100644 --- a/msa/tb/docker-tb/Dockerfile +++ b/msa/tb/docker-tb/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/ diff --git a/msa/tb/docker/thingsboard.conf b/msa/tb/docker/thingsboard.conf index 0acc4662c3..e583d90715 100644 --- a/msa/tb/docker/thingsboard.conf +++ b/msa/tb/docker/thingsboard.conf @@ -15,10 +15,10 @@ # export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" -export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/thingsboard/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" export LOG_FILENAME=thingsboard.out export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile index 6f377a0d91..813c6d76f2 100644 --- a/msa/transport/coap/docker/Dockerfile +++ b/msa/transport/coap/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile index d29259384b..62cbe1d5f9 100644 --- a/msa/transport/http/docker/Dockerfile +++ b/msa/transport/http/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/lwm2m/docker/Dockerfile b/msa/transport/lwm2m/docker/Dockerfile index ecdf3ec1c6..9b4c2d60c2 100644 --- a/msa/transport/lwm2m/docker/Dockerfile +++ b/msa/transport/lwm2m/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-lwm2m-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile index 1052f89f78..ee41f227db 100644 --- a/msa/transport/mqtt/docker/Dockerfile +++ b/msa/transport/mqtt/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index c20b20ada2..84906e42db 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -40,17 +40,4 @@ coap lwm2m - - - - - - com.spotify - dockerfile-maven-plugin - 1.4.5 - - - - - diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 9bd14a9b91..742aa039be 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -71,7 +71,7 @@ org.apache.maven.plugins maven-jar-plugin - 2.4 + 3.1.1 diff --git a/packaging/java/build.gradle b/packaging/java/build.gradle index aed7f8a5ba..fcc937e53f 100644 --- a/packaging/java/build.gradle +++ b/packaging/java/build.gradle @@ -102,7 +102,7 @@ buildRpm { archiveVersion = projectVersion.replace('-', '') archiveFileName = "${pkgName}.rpm" - requires("java-1.8.0") + requires("java-11") from("${buildDir}/conf") { include "${pkgName}.conf" @@ -141,7 +141,7 @@ buildDeb { archiveFileName = "${pkgName}.deb" - requires("openjdk-8-jre").or("java8-runtime").or("oracle-java8-installer").or("openjdk-8-jre-headless") + requires("openjdk-11-jre").or("java11-runtime").or("oracle-java11-installer").or("openjdk-11-jre-headless") from("${buildDir}/conf") { include "${pkgName}.conf" diff --git a/packaging/java/scripts/windows/install.bat b/packaging/java/scripts/windows/install.bat index ffd085aa20..6f168c76d8 100644 --- a/packaging/java/scripts/windows/install.bat +++ b/packaging/java/scripts/windows/install.bat @@ -7,11 +7,11 @@ setlocal ENABLEEXTENSIONS for /f tokens^=2-5^ delims^=.-_^" %%j in ('java -fullversion 2^>^&1') do set "jver=%%j%%k" @ECHO CurrentVersion %jver% -if %jver% NEQ 18 GOTO JAVA_NOT_INSTALLED +if %jver% NEQ 110 GOTO JAVA_NOT_INSTALLED :JAVA_INSTALLED -@ECHO Java 1.8 found! +@ECHO Java 11 found! @ECHO Installing thingsboard ... SET loadDemo=false @@ -50,8 +50,8 @@ POPD GOTO END :JAVA_NOT_INSTALLED -@ECHO Java 1.8 is not installed. Only Java 1.8 is supported -@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 1.8. Then retry installation. +@ECHO Java 11 is not installed. Only Java 11 is supported +@ECHO Please go to https://adoptopenjdk.net/index.html and install Java 11. Then retry installation. PAUSE GOTO END diff --git a/packaging/java/scripts/windows/service.xml b/packaging/java/scripts/windows/service.xml index 2cc46a7e91..7b077142c4 100644 --- a/packaging/java/scripts/windows/service.xml +++ b/packaging/java/scripts/windows/service.xml @@ -10,29 +10,18 @@ java -Dplatform=windows -Dinstall.data_dir=%BASE%\data - -Xloggc:%BASE%\logs\gc.log + -Xlog:gc*,heap*,age*,safepoint=debug:file=%BASE%\logs\gc.log:time,uptime,level,tags:filecount=10,filesize=10M -XX:+HeapDumpOnOutOfMemoryError - -XX:+PrintGCDetails - -XX:+PrintGCDateStamps - -XX:+PrintHeapAtGC - -XX:+PrintTenuringDistribution - -XX:+PrintGCApplicationStoppedTime - -XX:+UseGCLogFileRotation - -XX:NumberOfGCLogFiles=10 - -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark - -XX:CMSWaitDuration=10000 - -XX:+UseParNewGC - -XX:+UseConcMarkSweepGC - -XX:+CMSParallelRemarkEnabled - -XX:+CMSParallelInitialMarkEnabled - -XX:+CMSEdenChunksRecordAlways - -XX:CMSInitiatingOccupancyFraction=75 - -XX:+UseCMSInitiatingOccupancyOnly + -XX:+UseG1GC + -XX:MaxGCPauseMillis=500 + -XX:+UseStringDeduplication + -XX:+ParallelRefProcEnabled + -XX:MaxTenuringThreshold=10 -Xms512m -Xmx1024m -jar diff --git a/pom.xml b/pom.xml index 56d848f4c9..72f5ed13af 100755 --- a/pom.xml +++ b/pom.xml @@ -36,6 +36,9 @@ ${project.name} /var/log/${pkg.name} /usr/share/${pkg.name} + 1.3.2 + 2.3.2 + 2.3.2 2.3.5.RELEASE 5.2.10.RELEASE 5.4.1 @@ -46,9 +49,9 @@ 4.12 1.7.7 1.2.3 - 1.9.5 + 3.3.3 0.10 - 4.6.0 + 4.10.0 4.0.5 4.3.1.0 3.11.9 @@ -58,9 +61,9 @@ 3.4 2.5 1.4 - 2.11.3 - 2.11.3 - 2.11.3 + 2.12.1 + 2.12.1 + 2.12.1 2.2.6 2.6.0 1.3.0 @@ -73,7 +76,7 @@ 3.5.5 3.11.4 1.22.1 - 1.16.18 + 1.18.18 1.2.4 4.1.53.Final 1.5.0 @@ -89,12 +92,12 @@ 2.5.0 2.5.3 1.2.1 - 9.4.1212 + 42.2.16 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* 5.0.2 - 0.1.14 + 0.1.31 2.6.0 4.1.1 2.57 @@ -581,8 +584,19 @@ maven-compiler-plugin 3.8.1 - 1.8 - 1.8 + 11 + + -Xlint:deprecation + -Xlint:removal + -Xlint:unchecked + + + + org.projectlombok + lombok + ${lombok.version} + + @@ -593,12 +607,12 @@ org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.2.1 org.apache.maven.plugins maven-jar-plugin - 3.0.2 + 3.1.1 org.apache.maven.plugins @@ -619,12 +633,22 @@ org.apache.maven.plugins maven-surefire-plugin 3.0.0-M1 + + + --illegal-access=permit + + org.apache.maven.plugins maven-install-plugin 3.0.0-M1 + + org.apache.maven.plugins + maven-deploy-plugin + 3.0.0-M1 + org.apache.maven.plugins maven-dependency-plugin @@ -922,6 +946,21 @@ test-jar test + + javax.annotation + javax.annotation-api + ${javax-annotation.version} + + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind-api.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + org.springframework.boot spring-boot-starter-security @@ -1303,7 +1342,7 @@ org.mockito - mockito-all + mockito-core ${mockito.version} test diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index cc7b57fbe8..627184c181 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -1204,7 +1204,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { public List getDeviceTypes() { return restTemplate.exchange( - baseURL + "/api/devices", + baseURL + "/api/device/types", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index c303c6984e..bb348793c1 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -88,6 +88,17 @@ javax.mail provided + + junit + junit + ${junit.version} + test + + + org.mockito + mockito-core + test + diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 9809926db7..e4a71ee33d 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNodeState; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -166,6 +167,10 @@ public interface TbContext { RuleNodeId getSelfId(); + RuleNode getSelf(); + + String getRuleChainName(); + TenantId getTenantId(); AttributesService getAttributesService(); @@ -234,9 +239,6 @@ public interface TbContext { TbResultSetFuture submitCassandraTask(CassandraStatementTask task); - @Deprecated - RedisTemplate getRedisTemplate(); - PageData findRuleNodeStates(PageLink pageLink); RuleNodeState findRuleNodeStateForEntity(EntityId entityId); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java index 5f518dd66e..4b039dbf53 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java @@ -33,6 +33,8 @@ import java.util.Set; @Data public class DeviceCredentialsUpdateNotificationMsg implements ToDeviceActorNotificationMsg { + private static final long serialVersionUID = -3956907402411126990L; + private final TenantId tenantId; private final DeviceId deviceId; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java index e92f0f2842..0418986998 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java @@ -24,6 +24,9 @@ import org.thingsboard.server.common.msg.MsgType; @Data @AllArgsConstructor public class DeviceNameOrTypeUpdateMsg implements ToDeviceActorNotificationMsg { + + private static final long serialVersionUID = -5738949227650536685L; + private final TenantId tenantId; private final DeviceId deviceId; private final String deviceName; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java index 8804320295..f44c2a48d6 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java @@ -16,15 +16,20 @@ package org.thingsboard.rule.engine.api.util; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -34,8 +39,11 @@ public class TbNodeUtils { private static final ObjectMapper mapper = new ObjectMapper(); - private static final String VARIABLE_TEMPLATE = "${%s}"; + private static final String METADATA_VARIABLE_TEMPLATE = "${%s}"; + private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])"); + + private static final String DATA_VARIABLE_TEMPLATE = "$[%s]"; public static T convert(TbNodeConfiguration configuration, Class clazz) throws TbNodeException { try { @@ -45,6 +53,43 @@ public class TbNodeUtils { } } + public static List processPatterns(List patterns, TbMsg tbMsg) { + if (!CollectionUtils.isEmpty(patterns)) { + return patterns.stream().map(p -> processPattern(p, tbMsg)).collect(Collectors.toList()); + } + return Collections.emptyList(); + } + + public static String processPattern(String pattern, TbMsg tbMsg) { + try { + String result = processPattern(pattern, tbMsg.getMetaData()); + JsonNode json = mapper.readTree(tbMsg.getData()); + if (json.isObject()) { + Matcher matcher = DATA_PATTERN.matcher(result); + while (matcher.find()) { + String group = matcher.group(2); + String[] keys = group.split("\\."); + JsonNode jsonNode = json; + for (String key : keys) { + if (!StringUtils.isEmpty(key) && jsonNode != null) { + jsonNode = jsonNode.get(key); + } else { + jsonNode = null; + break; + } + } + + if (jsonNode != null && jsonNode.isValueNode()) { + result = result.replace(String.format(DATA_VARIABLE_TEMPLATE, group), jsonNode.asText()); + } + } + } + return result; + } catch (Exception e) { + throw new RuntimeException("Failed to process pattern!", e); + } + } + public static List processPatterns(List patterns, TbMsgMetaData metaData) { if (!CollectionUtils.isEmpty(patterns)) { return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList()); @@ -53,15 +98,15 @@ public class TbNodeUtils { } public static String processPattern(String pattern, TbMsgMetaData metaData) { - String result = new String(pattern); - for (Map.Entry keyVal : metaData.values().entrySet()) { + String result = pattern; + for (Map.Entry keyVal : metaData.values().entrySet()) { result = processVar(result, keyVal.getKey(), keyVal.getValue()); } return result; } private static String processVar(String pattern, String key, String val) { - String varPattern = String.format(VARIABLE_TEMPLATE, key); + String varPattern = String.format(METADATA_VARIABLE_TEMPLATE, key); return pattern.replace(varPattern, val); } diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java new file mode 100644 index 0000000000..f1e45d2e8d --- /dev/null +++ b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api.util; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; + +@RunWith(MockitoJUnitRunner.class) +public class TbNodeUtilsTest { + + @Test + public void testSimpleReplacement() { + String pattern = "ABC ${metadata_key} $[data_key]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("metadata_key", "metadata_value"); + + ObjectNode node = JacksonUtil.newObjectNode(); + node.put("data_key", "data_value"); + + TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + String result = TbNodeUtils.processPattern(pattern, msg); + Assert.assertEquals("ABC metadata_value data_value", result); + } + + @Test + public void testNoReplacement() { + String pattern = "ABC ${metadata_key} $[data_key]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("key", "metadata_value"); + + ObjectNode node = JacksonUtil.newObjectNode(); + node.put("key", "data_value"); + + TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + String result = TbNodeUtils.processPattern(pattern, msg); + Assert.assertEquals(pattern, result); + } + + @Test + public void testSameKeysReplacement() { + String pattern = "ABC ${key} $[key]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("key", "metadata_value"); + + ObjectNode node = JacksonUtil.newObjectNode(); + node.put("key", "data_value"); + + TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + String result = TbNodeUtils.processPattern(pattern, msg); + Assert.assertEquals("ABC metadata_value data_value", result); + } + + @Test + public void testComplexObjectReplacement() { + String pattern = "ABC ${key} $[key1.key2.key3]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("key", "metadata_value"); + + ObjectNode key2Node = JacksonUtil.newObjectNode(); + key2Node.put("key3", "value3"); + + ObjectNode key1Node = JacksonUtil.newObjectNode(); + key1Node.set("key2", key2Node); + + + ObjectNode node = JacksonUtil.newObjectNode(); + node.set("key1", key1Node); + + TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + String result = TbNodeUtils.processPattern(pattern, msg); + Assert.assertEquals("ABC metadata_value value3", result); + } + + @Test + public void testArrayReplacementDoesNotWork() { + String pattern = "ABC ${key} $[key1.key2[0].key3]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("key", "metadata_value"); + + ObjectNode key2Node = JacksonUtil.newObjectNode(); + key2Node.put("key3", "value3"); + + ObjectNode key1Node = JacksonUtil.newObjectNode(); + key1Node.set("key2", key2Node); + + + ObjectNode node = JacksonUtil.newObjectNode(); + node.set("key1", key1Node); + + TbMsg msg = TbMsg.newMsg("CUSTOM", TenantId.SYS_TENANT_ID, md, JacksonUtil.toString(node)); + String result = TbNodeUtils.processPattern(pattern, msg); + Assert.assertEquals("ABC metadata_value $[key1.key2[0].key3]", result); + } + +} diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 0084e0e531..31c0c1d824 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -148,7 +148,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java index 482e5261f0..39f8b6fa3a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractCustomerActionNode.java @@ -48,7 +48,7 @@ public abstract class TbAbstractCustomerActionNode cacheBuilder = CacheBuilder.newBuilder(); if (this.config.getCustomerCacheExpiration() > 0) { cacheBuilder.expireAfterWrite(this.config.getCustomerCacheExpiration(), TimeUnit.SECONDS); } @@ -79,7 +79,7 @@ public abstract class TbAbstractCustomerActionNode getCustomer(TbContext ctx, TbMsg msg) { - String customerTitle = TbNodeUtils.processPattern(this.config.getCustomerNamePattern(), msg.getMetaData()); + String customerTitle = TbNodeUtils.processPattern(this.config.getCustomerNamePattern(), msg); CustomerKey key = new CustomerKey(customerTitle); return ctx.getDbCallbackExecutor().executeAsync(() -> { Optional customerId = customerIdCache.get(key); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 2ed69e91a9..861e2a3f0d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -72,7 +72,7 @@ public abstract class TbAbstractRelationActionNode cacheBuilder = CacheBuilder.newBuilder(); if (this.config.getEntityCacheExpiration() > 0) { cacheBuilder.expireAfterWrite(this.config.getEntityCacheExpiration(), TimeUnit.SECONDS); } @@ -142,7 +142,7 @@ public abstract class TbAbstractRelationActionNode processAlarm(TbContext ctx, TbMsg msg) { - String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData()); + String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg); ListenableFuture alarmFuture; if (msg.getOriginator().getEntityType().equals(EntityType.ALARM)) { alarmFuture = ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), new AlarmId(msg.getOriginator().getId())); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index aab1ce1e5b..8282c74159 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -76,7 +76,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { if (!msg.getMetaData().getData().isEmpty()) { long now = System.currentTimeMillis(); String scope = msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ? - DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue("scope"); + DataConstants.CLIENT_SCOPE : msg.getMetaData().getValue(DataConstants.SCOPE); ListenableFuture> entityViewsFuture = ctx.getEntityViewService().findEntityViewsByTenantIdAndEntityIdAsync(ctx.getTenantId(), msg.getOriginator()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index dcae930fbd..c941d6ca92 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -70,7 +70,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode messageAttributes = new HashMap<>(); this.config.getMessageAttributes().forEach((k,v) -> { - String name = TbNodeUtils.processPattern(k, msg.getMetaData()); - String val = TbNodeUtils.processPattern(v, msg.getMetaData()); + String name = TbNodeUtils.processPattern(k, msg); + String val = TbNodeUtils.processPattern(v, msg); messageAttributes.put(name, new MessageAttributeValue().withDataType("String").withStringValue(val)); }); sendMsgRequest.setMessageAttributes(messageAttributes); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index b61e2d91ab..fc6ee959e0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -109,9 +109,10 @@ public class TbMsgGeneratorNode implements TbNode { } }, t -> { - if (initialized) { + if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); scheduleTickMsg(ctx); + currentMsgCount++; } }); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index c91028a965..730cb734be 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -83,7 +83,7 @@ public class TbMsgDelayNode implements TbNode { int periodInSeconds; if (config.isUseMetadataPeriodInSecondsPatterns()) { if (isParsable(msg, config.getPeriodInSecondsPattern())) { - periodInSeconds = Integer.parseInt(TbNodeUtils.processPattern(config.getPeriodInSecondsPattern(), msg.getMetaData())); + periodInSeconds = Integer.parseInt(TbNodeUtils.processPattern(config.getPeriodInSecondsPattern(), msg)); } else { throw new RuntimeException("Can't parse period in seconds from metadata using pattern: " + config.getPeriodInSecondsPattern()); } @@ -94,7 +94,7 @@ public class TbMsgDelayNode implements TbNode { } private boolean isParsable(TbMsg msg, String pattern) { - return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData())); + return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg)); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java index 3c7a4f607b..8599a50ad8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/gcp/pubsub/TbPubSubNode.java @@ -93,8 +93,8 @@ public class TbPubSubNode implements TbNode { PubsubMessage.Builder pubsubMessageBuilder = PubsubMessage.newBuilder(); pubsubMessageBuilder.setData(data); this.config.getMessageAttributes().forEach((k, v) -> { - String name = TbNodeUtils.processPattern(k, msg.getMetaData()); - String val = TbNodeUtils.processPattern(v, msg.getMetaData()); + String name = TbNodeUtils.processPattern(k, msg); + String val = TbNodeUtils.processPattern(v, msg); pubsubMessageBuilder.putAttributes(name, val); }); ApiFuture messageIdFuture = this.pubSubClient.publish(pubsubMessageBuilder.build()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java index 6ce7de8baf..b5612dfc6a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java @@ -62,7 +62,7 @@ public class GeoUtil { } polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normY(firstLat)); Shape shape = polygonBuilder.buildOrRect(); - Point point = jtsCtx.makePoint(coordinates.getLongitude(), coordinates.getLatitude()); + Point point = jtsCtx.getShapeFactory().pointXY(coordinates.getLongitude(), coordinates.getLatitude()); return shape.relate(point).equals(SpatialRelation.CONTAINS); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 89cc747811..0ac03e1afb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -17,11 +17,13 @@ package org.thingsboard.rule.engine.kafka; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.header.internals.RecordHeaders; @@ -73,8 +75,8 @@ public class TbKafkaNode implements TbNode { Properties properties = new Properties(); properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId()); properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); - properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, config.getValueSerializer()); - properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, config.getKeySerializer()); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, getKafkaSerializerClass(config.getValueSerializer())); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, getKafkaSerializerClass(config.getKeySerializer())); properties.put(ProducerConfig.ACKS_CONFIG, config.getAcks()); properties.put(ProducerConfig.RETRIES_CONFIG, config.getRetries()); properties.put(ProducerConfig.BATCH_SIZE_CONFIG, config.getBatchSize()); @@ -92,9 +94,22 @@ public class TbKafkaNode implements TbNode { } } + private Class getKafkaSerializerClass(String serializerClassName) { + Class serializerClass = null; + if (!StringUtils.isEmpty(serializerClassName)) { + try { + serializerClass = Class.forName(serializerClassName); + } catch (ClassNotFoundException e) {} + } + if (serializerClass == null) { + serializerClass = StringSerializer.class; + } + return serializerClass; + } + @Override public void onMsg(TbContext ctx, TbMsg msg) { - String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); + String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg); try { ctx.getExternalCallExecutor().executeAsync(() -> { publish(ctx, msg, topic); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 6333a6db9f..c8415e3181 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -76,18 +76,18 @@ public class TbMsgToEmailNode implements TbNode { private EmailPojo convert(TbMsg msg) throws IOException { EmailPojo.EmailPojoBuilder builder = EmailPojo.builder(); - builder.from(fromTemplate(this.config.getFromTemplate(), msg.getMetaData())); - builder.to(fromTemplate(this.config.getToTemplate(), msg.getMetaData())); - builder.cc(fromTemplate(this.config.getCcTemplate(), msg.getMetaData())); - builder.bcc(fromTemplate(this.config.getBccTemplate(), msg.getMetaData())); - builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg.getMetaData())); - builder.body(fromTemplate(this.config.getBodyTemplate(), msg.getMetaData())); + builder.from(fromTemplate(this.config.getFromTemplate(), msg)); + builder.to(fromTemplate(this.config.getToTemplate(), msg)); + builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); + builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); + builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg)); + builder.body(fromTemplate(this.config.getBodyTemplate(), msg)); return builder.build(); } - private String fromTemplate(String template, TbMsgMetaData metaData) { + private String fromTemplate(String template, TbMsg msg) { if (!StringUtils.isEmpty(template)) { - return TbNodeUtils.processPattern(template, metaData); + return TbNodeUtils.processPattern(template, msg); } else { return null; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java new file mode 100644 index 0000000000..63b326a816 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java @@ -0,0 +1,185 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.metadata; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Slf4j +@RuleNode(type = ComponentType.ENRICHMENT, + name = "calculate delta", relationTypes = {"Success", "Failure", "Other"}, + configClazz = CalculateDeltaNodeConfiguration.class, + nodeDescription = "Calculates and adds 'delta' value into message based on the incoming and previous value", + nodeDetails = "Calculates delta and period based on the previous time-series reading and current data. " + + "Delta calculation is done in scope of the message originator, e.g. device, asset or customer. " + + "If there is input key, the output relation will be 'Success' unless delta is negative and corresponding configuration parameter is set. " + + "If there is no input value key in the incoming message, the output relation will be 'Other'.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbEnrichmentNodeCalculateDeltaConfig") +public class CalculateDeltaNode implements TbNode { + private Map cache; + private CalculateDeltaNodeConfiguration config; + private TbContext ctx; + private TimeseriesService timeseriesService; + private boolean useCache; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, CalculateDeltaNodeConfiguration.class); + this.ctx = ctx; + this.timeseriesService = ctx.getTimeseriesService(); + this.useCache = config.isUseCache(); + + if (useCache) { + cache = new ConcurrentHashMap<>(); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + JsonNode json = JacksonUtil.toJsonNode(msg.getData()); + String inputKey = config.getInputValueKey(); + if (json.has(inputKey)) { + DonAsynchron.withCallback(getLastValue(msg.getOriginator()), + previousData -> { + double currentValue = json.get(inputKey).asDouble(); + long currentTs = TbMsgTimeseriesNode.getTs(msg); + + if (useCache) { + cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue)); + } + + BigDecimal delta = BigDecimal.valueOf(previousData != null ? currentValue - previousData.value : 0.0); + + if (config.isTellFailureIfDeltaIsNegative() && delta.doubleValue() < 0) { + ctx.tellNext(msg, TbRelationTypes.FAILURE); + return; + } + + + if (config.getRound() != null) { + delta = delta.setScale(config.getRound(), RoundingMode.HALF_UP); + } + + ObjectNode result = (ObjectNode) json; + if (delta.stripTrailingZeros().scale() > 0) { + result.put(config.getOutputValueKey(), delta.doubleValue()); + } else { + result.put(config.getOutputValueKey(), delta.longValueExact()); + } + + if (config.isAddPeriodBetweenMsgs()) { + long period = previousData != null ? currentTs - previousData.ts : 0; + result.put(config.getPeriodValueKey(), period); + } + ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(result))); + }, + t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); + } else { + ctx.tellNext(msg, "Other"); + } + } else { + ctx.tellNext(msg, "Other"); + } + } + + @Override + public void destroy() { + if (useCache) { + cache.clear(); + } + } + + private ListenableFuture fetchLatestValue(EntityId entityId) { + return Futures.transform(timeseriesService.findLatest(ctx.getTenantId(), entityId, Collections.singletonList(config.getInputValueKey())), + list -> extractValue(list.get(0)) + , ctx.getDbCallbackExecutor()); + } + + private ListenableFuture getLastValue(EntityId entityId) { + ValueWithTs latestValue; + if (useCache && (latestValue = cache.get(entityId)) != null) { + return Futures.immediateFuture(latestValue); + } else { + return fetchLatestValue(entityId); + } + } + + private ValueWithTs extractValue(TsKvEntry kvEntry) { + if (kvEntry == null || kvEntry.getValue() == null) { + return null; + } + double result = 0.0; + long ts = kvEntry.getTs(); + switch (kvEntry.getDataType()) { + case LONG: + result = kvEntry.getLongValue().get(); + break; + case DOUBLE: + result = kvEntry.getDoubleValue().get(); + break; + case STRING: + try { + result = Double.parseDouble(kvEntry.getStrValue().get()); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Calculation failed. Unable to parse value [" + kvEntry.getStrValue().get() + "]" + + " of telemetry [" + kvEntry.getKey() + "] to Double"); + } + break; + case BOOLEAN: + throw new IllegalArgumentException("Calculation failed. Boolean values are not supported!"); + case JSON: + throw new IllegalArgumentException("Calculation failed. JSON values are not supported!"); + } + return new ValueWithTs(ts, result); + } + + private static class ValueWithTs { + private final long ts; + private final double value; + + private ValueWithTs(long ts, double value) { + this.ts = ts; + this.value = value; + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java new file mode 100644 index 0000000000..bec93a1d90 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNodeConfiguration.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.metadata; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class CalculateDeltaNodeConfiguration implements NodeConfiguration { + private String inputValueKey; + private String outputValueKey; + private boolean useCache; + private boolean addPeriodBetweenMsgs; + private String periodValueKey; + private Integer round; + private boolean tellFailureIfDeltaIsNegative; + + @Override + public CalculateDeltaNodeConfiguration defaultConfiguration() { + CalculateDeltaNodeConfiguration configuration = new CalculateDeltaNodeConfiguration(); + configuration.setInputValueKey("pulseCounter"); + configuration.setOutputValueKey("delta"); + configuration.setUseCache(true); + configuration.setAddPeriodBetweenMsgs(false); + configuration.setPeriodValueKey("periodInMs"); + configuration.setTellFailureIfDeltaIsNegative(true); + return configuration; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index 5788b2e086..0bb8cc7cfc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; @@ -61,7 +62,7 @@ public abstract class TbAbstractGetAttributesNode> failuresMap = new ConcurrentHashMap<>(); ListenableFuture> allFutures = Futures.allAsList( - putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg.getMetaData()), failuresMap), - putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg.getMetaData()), failuresMap, "cs_"), - putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg.getMetaData()), failuresMap, "shared_"), - putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg.getMetaData()), failuresMap, "ss_") + putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresMap), + putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresMap, "cs_"), + putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresMap, "shared_"), + putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresMap, "ss_") ); withCallback(allFutures, i -> { if (!failuresMap.isEmpty()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index b17a8cd563..3c3fa8e21a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.metadata; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -89,7 +90,7 @@ public class TbGetTelemetryNode implements TbNode { orderByFetchAll = ASC_ORDER; } mapper = new ObjectMapper(); - mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false); + mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); } @@ -102,7 +103,7 @@ public class TbGetTelemetryNode implements TbNode { if (config.isUseMetadataIntervalPatterns()) { checkMetadataKeyPatterns(msg); } - List keys = TbNodeUtils.processPatterns(tsKeyNames, msg.getMetaData()); + List keys = TbNodeUtils.processPatterns(tsKeyNames, msg); ListenableFuture> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg, keys)); DonAsynchron.withCallback(list, data -> { process(data, msg, keys); @@ -196,10 +197,10 @@ public class TbGetTelemetryNode implements TbNode { Interval interval = new Interval(); if (config.isUseMetadataIntervalPatterns()) { if (isParsable(msg, config.getStartIntervalPattern())) { - interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg.getMetaData()))); + interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg))); } if (isParsable(msg, config.getEndIntervalPattern())) { - interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg.getMetaData()))); + interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg))); } } else { long ts = System.currentTimeMillis(); @@ -210,7 +211,7 @@ public class TbGetTelemetryNode implements TbNode { } private boolean isParsable(TbMsg msg, String pattern) { - return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData())); + return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg)); } private void checkMetadataKeyPatterns(TbMsg msg) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 629b5cb86c..4f4dc7ab60 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -75,7 +75,7 @@ public class TbMqttNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg.getMetaData()); + String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg); this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) .addListener(future -> { if (future.isSuccess()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 51542132fc..499e9189cc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -55,8 +55,9 @@ class AlarmRuleState { private final Set entityKeys; private PersistedAlarmRuleState state; private boolean updateFlag; + private final DynamicPredicateValueCtx dynamicPredicateValueCtx; - AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set entityKeys, PersistedAlarmRuleState state) { + AlarmRuleState(AlarmSeverity severity, AlarmRule alarmRule, Set entityKeys, PersistedAlarmRuleState state, DynamicPredicateValueCtx dynamicPredicateValueCtx) { this.severity = severity; this.alarmRule = alarmRule; this.entityKeys = entityKeys; @@ -80,6 +81,7 @@ class AlarmRuleState { } this.requiredDurationInMs = requiredDurationInMs; this.requiredRepeats = requiredRepeats; + this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; } public boolean validateTsUpdate(Set changedKeys) { @@ -385,15 +387,28 @@ class AlarmRuleState { private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue value) { EntityKeyValue ekv = null; if (value.getDynamicValue() != null) { - ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); - if (ekv == null) { - ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); - if (ekv == null) { - ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + switch (value.getDynamicValue().getSourceType()) { + case CURRENT_DEVICE: + ekv = data.getValue(new EntityKey(EntityKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); if (ekv == null) { - ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + ekv = data.getValue(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + if (ekv == null) { + ekv = data.getValue(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + if (ekv == null) { + ekv = data.getValue(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); + } + } } - } + if(ekv != null || !value.getDynamicValue().isInherit()) { + break; + } + case CURRENT_CUSTOMER: + ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); + if(ekv != null || !value.getDynamicValue().isInherit()) { + break; + } + case CURRENT_TENANT: + ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); } } return ekv; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index e864167c0d..7af0beb505 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -25,11 +25,14 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.profile.state.PersistedAlarmRuleState; import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; @@ -57,10 +60,12 @@ class AlarmState { private volatile TbMsgMetaData lastMsgMetaData; private volatile String lastMsgQueueName; private volatile DataSnapshot dataSnapshot; + private final DynamicPredicateValueCtx dynamicPredicateValueCtx; - AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { + AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState, DynamicPredicateValueCtx dynamicPredicateValueCtx) { this.deviceProfile = deviceProfile; this.originator = originator; + this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; this.updateState(alarmDefinition, alarmState); } @@ -188,12 +193,12 @@ class AlarmState { } } createRulesSortedBySeverityDesc.add(new AlarmRuleState(severity, rule, - deviceProfile.getCreateAlarmKeys(alarm.getId(), severity), ruleState)); + deviceProfile.getCreateAlarmKeys(alarm.getId(), severity), ruleState, dynamicPredicateValueCtx)); }); createRulesSortedBySeverityDesc.sort(Comparator.comparingInt(state -> state.getSeverity().ordinal())); PersistedAlarmRuleState ruleState = alarmState == null ? null : alarmState.getClearRuleState(); if (alarmDefinition.getClearRule() != null) { - clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), deviceProfile.getClearAlarmKeys(alarm.getId()), ruleState); + clearState = new AlarmRuleState(null, alarmDefinition.getClearRule(), deviceProfile.getClearAlarmKeys(alarm.getId()), ruleState, dynamicPredicateValueCtx); } } @@ -223,7 +228,11 @@ class AlarmState { currentAlarm.setType(alarmDefinition.getAlarmType()); currentAlarm.setStatus(AlarmStatus.ACTIVE_UNACK); currentAlarm.setSeverity(severity); - currentAlarm.setStartTs(System.currentTimeMillis()); + long startTs = dataSnapshot.getTs(); + if (startTs == 0L) { + startTs = System.currentTimeMillis(); + } + currentAlarm.setStartTs(startTs); currentAlarm.setEndTs(currentAlarm.getStartTs()); currentAlarm.setDetails(createDetails(ruleState)); currentAlarm.setOriginator(originator); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index 13dd0ea66d..bb7d5f8862 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -63,11 +63,15 @@ class DeviceState { private PersistedDeviceState pds; private DataSnapshot latestValues; private final ConcurrentMap alarmStates = new ConcurrentHashMap<>(); + private final DynamicPredicateValueCtx dynamicPredicateValueCtx; DeviceState(TbContext ctx, TbDeviceProfileNodeConfiguration config, DeviceId deviceId, ProfileState deviceProfile, RuleNodeState state) { this.persistState = config.isPersistAlarmRulesState(); this.deviceId = deviceId; this.deviceProfile = deviceProfile; + + this.dynamicPredicateValueCtx = new DynamicPredicateValueCtxImpl(ctx.getTenantId(), deviceId, ctx); + if (config.isPersistAlarmRulesState()) { if (state != null) { this.state = state; @@ -87,7 +91,7 @@ class DeviceState { if (pds != null) { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); } } } @@ -108,7 +112,7 @@ class DeviceState { if (alarmStates.containsKey(alarm.getId())) { alarmStates.get(alarm.getId()).updateState(alarm, getOrInitPersistedAlarmState(alarm)); } else { - alarmStates.putIfAbsent(alarm.getId(), new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + alarmStates.putIfAbsent(alarm.getId(), new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); } } } @@ -134,6 +138,8 @@ class DeviceState { stateChanged = processTelemetry(ctx, msg); } else if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { stateChanged = processAttributesUpdateRequest(ctx, msg); + } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT) || msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { + stateChanged = processDeviceActivityEvent(ctx, msg); } else if (msg.getType().equals(DataConstants.ATTRIBUTES_UPDATED)) { stateChanged = processAttributesUpdateNotification(ctx, msg); } else if (msg.getType().equals(DataConstants.ATTRIBUTES_DELETED)) { @@ -143,6 +149,9 @@ class DeviceState { } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { processAlarmAckNotification(ctx, msg); } else { + if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED) || msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { + dynamicPredicateValueCtx.resetCustomer(); + } ctx.tellSuccess(msg); } if (persistState && stateChanged) { @@ -151,12 +160,21 @@ class DeviceState { } } + private boolean processDeviceActivityEvent(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { + String scope = msg.getMetaData().getValue(DataConstants.SCOPE); + if (StringUtils.isEmpty(scope)) { + return processTelemetry(ctx, msg); + } else { + return processAttributes(ctx, msg, scope); + } + } + private boolean processAlarmClearNotification(TbContext ctx, TbMsg msg) { boolean stateChanged = false; Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); stateChanged |= alarmState.processAlarmClear(ctx, alarmNf); } ctx.tellSuccess(msg); @@ -167,26 +185,25 @@ class DeviceState { Alarm alarmNf = JacksonUtil.fromString(msg.getData(), Alarm.class); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); alarmState.processAckAlarm(alarmNf); } ctx.tellSuccess(msg); } private boolean processAttributesUpdateNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); - String scope = msg.getMetaData().getValue("scope"); + String scope = msg.getMetaData().getValue(DataConstants.SCOPE); if (StringUtils.isEmpty(scope)) { scope = DataConstants.CLIENT_SCOPE; } - return processAttributesUpdate(ctx, msg, attributes, scope); + return processAttributes(ctx, msg, scope); } private boolean processAttributesDeleteNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { boolean stateChanged = false; List keys = new ArrayList<>(); new JsonParser().parse(msg.getData()).getAsJsonObject().get("attributes").getAsJsonArray().forEach(e -> keys.add(e.getAsString())); - String scope = msg.getMetaData().getValue("scope"); + String scope = msg.getMetaData().getValue(DataConstants.SCOPE); if (StringUtils.isEmpty(scope)) { scope = DataConstants.CLIENT_SCOPE; } @@ -195,7 +212,7 @@ class DeviceState { keys.forEach(key -> latestValues.removeValue(new EntityKey(keyType, key))); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); stateChanged |= alarmState.process(ctx, msg, latestValues, null); } } @@ -204,17 +221,17 @@ class DeviceState { } protected boolean processAttributesUpdateRequest(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { - Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); - return processAttributesUpdate(ctx, msg, attributes, DataConstants.CLIENT_SCOPE); + return processAttributes(ctx, msg, DataConstants.CLIENT_SCOPE); } - private boolean processAttributesUpdate(TbContext ctx, TbMsg msg, Set attributes, String scope) throws ExecutionException, InterruptedException { + private boolean processAttributes(TbContext ctx, TbMsg msg, String scope) throws ExecutionException, InterruptedException { boolean stateChanged = false; + Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(msg.getData())); if (!attributes.isEmpty()) { SnapshotUpdate update = merge(latestValues, attributes, scope); for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); stateChanged |= alarmState.process(ctx, msg, latestValues, update); } } @@ -233,7 +250,7 @@ class DeviceState { if (update.hasUpdate()) { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), - a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm))); + a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); stateChanged |= alarmState.process(ctx, msg, latestValues, update); } } @@ -374,7 +391,7 @@ class DeviceState { } } - private EntityKeyValue toEntityValue(KvEntry entry) { + public static EntityKeyValue toEntityValue(KvEntry entry) { switch (entry.getDataType()) { case STRING: return EntityKeyValue.fromString(entry.getStrValue().get()); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtx.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtx.java new file mode 100644 index 0000000000..3e8df05d4a --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtx.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.profile; + +public interface DynamicPredicateValueCtx { + + EntityKeyValue getTenantValue(String key); + + EntityKeyValue getCustomerValue(String key); + + void resetCustomer(); +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java new file mode 100644 index 0000000000..18140040c7 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DynamicPredicateValueCtxImpl.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.profile; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; + +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +@Slf4j +public class DynamicPredicateValueCtxImpl implements DynamicPredicateValueCtx { + private final TenantId tenantId; + private CustomerId customerId; + private final DeviceId deviceId; + private final TbContext ctx; + + public DynamicPredicateValueCtxImpl(TenantId tenantId, DeviceId deviceId, TbContext ctx) { + this.tenantId = tenantId; + this.deviceId = deviceId; + this.ctx = ctx; + resetCustomer(); + } + + @Override + public EntityKeyValue getTenantValue(String key) { + return getValue(tenantId, key); + } + + @Override + public EntityKeyValue getCustomerValue(String key) { + return customerId == null || customerId.isNullUid() ? null : getValue(customerId, key); + } + + @Override + public void resetCustomer() { + Device device = ctx.getDeviceService().findDeviceById(tenantId, deviceId); + if (device != null) { + this.customerId = device.getCustomerId(); + } + } + + private EntityKeyValue getValue(EntityId entityId, String key) { + try { + Optional entry = ctx.getAttributesService().find(tenantId, entityId, DataConstants.SERVER_SCOPE, key).get(); + if (entry.isPresent()) { + return DeviceState.toEntityValue(entry.get()); + } + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to get attribute by key: {} for {}: [{}]", key, entityId.getEntityType(), entityId.getId()); + } + return null; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java index 7e7f407031..08a230d716 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java @@ -95,7 +95,9 @@ class ProfileState { case NUMERIC: case BOOLEAN: DynamicValue value = ((SimpleKeyFilterPredicate) predicate).getValue().getDynamicValue(); - if (value != null && value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE) { + if (value != null && (value.getSourceType() == DynamicValueSourceType.CURRENT_TENANT || + value.getSourceType() == DynamicValueSourceType.CURRENT_CUSTOMER || + value.getSourceType() == DynamicValueSourceType.CURRENT_DEVICE)) { EntityKey entityKey = new EntityKey(EntityKeyType.ATTRIBUTE, value.getSourceAttribute()); entityKeys.add(entityKey); ruleKeys.add(entityKey); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 3faac79420..9b891ea8eb 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -134,7 +134,8 @@ public class TbDeviceProfileNode implements TbNode { if (deviceState != null) { deviceState.process(ctx, msg); } else { - ctx.tellFailure(msg, new IllegalStateException("Device profile for device [" + deviceId + "] not found!")); + log.info("Device was not found! Most probably device [" + deviceId + "] has been removed from the database. Acknowledging msg."); + ctx.ack(msg); } } } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java index d6a0dfb8fb..fea622bf30 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -97,11 +97,11 @@ public class TbRabbitMqNode implements TbNode { private TbMsg publishMessage(TbContext ctx, TbMsg msg) throws Exception { String exchangeName = ""; if (!StringUtils.isEmpty(this.config.getExchangeNamePattern())) { - exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg.getMetaData()); + exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg); } String routingKey = ""; if (!StringUtils.isEmpty(this.config.getRoutingKeyPattern())) { - routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg.getMetaData()); + routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg); } AMQP.BasicProperties properties = null; if (!StringUtils.isEmpty(this.config.getMessageProperties())) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 17dc6b5bce..c485489954 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -35,7 +35,6 @@ import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory; -import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.StringUtils; import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFutureCallback; @@ -58,12 +57,12 @@ import java.net.PasswordAuthentication; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.util.Deque; -import java.util.Optional; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.TimeUnit; @Data @Slf4j +@SuppressWarnings("deprecation") public class TbHttpClient { private static final String STATUS = "status"; @@ -172,8 +171,8 @@ public class TbHttpClient { } public void processMessage(TbContext ctx, TbMsg msg) { - String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData()); - HttpHeaders headers = prepareHeaders(msg.getMetaData()); + String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); + HttpHeaders headers = prepareHeaders(msg); HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); HttpEntity entity = new HttpEntity<>(msg.getData(), headers); @@ -232,9 +231,9 @@ public class TbHttpClient { return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); } - private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { + private HttpHeaders prepareHeaders(TbMsg msg) { HttpHeaders headers = new HttpHeaders(); - config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData))); + config.getHeaders().forEach((k, v) -> headers.add(TbNodeUtils.processPattern(k, msg), TbNodeUtils.processPattern(v, msg))); ClientCredentials credentials = config.getCredentials(); if (CredentialsType.BASIC == credentials.getType()) { BasicCredentials basicCredentials = (BasicCredentials) credentials; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index 40b79163b8..b3eb982287 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -61,4 +61,12 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var G,U,j,H=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(G||(G={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var z,$=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=Q,n.credentialsTypeTranslationsMap=_,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=C(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=b([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),se=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie,le],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ne,ae,oe,ie,le]})],e)}(),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,se],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe]})],e)}(),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(){function e(){}return e=b([t.NgModule({declarations:[Te,qe,Se,Ie,ke,Ne,Ve,Ee],imports:[r.CommonModule,a.SharedModule,se],exports:[Te,qe,Se,Ie,ke,Ne,Ve,Ee]})],e)}(),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=H,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(H.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(H.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=G,n.fetchModes=Object.keys(G),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===G.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(){function e(){}return e=b([t.NgModule({declarations:[Le,Me,Pe,Re,we,Oe,De,Ke],imports:[r.CommonModule,a.SharedModule,se],exports:[Le,Me,Pe,Re,we,Oe,De,Ke]})],e)}(),Ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(){function e(){}return e=b([t.NgModule({declarations:[Ge,Ue,je],imports:[r.CommonModule,a.SharedModule,se],exports:[Ge,Ue,je]})],e)}(),ze=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":"Comma separated Phone Numbers, use ${metaKeyName} to substitute variables from metadata","sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","sms-message-template-hint":"SMS message template, use ${metaKeyName} to substitute variables from metadata","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[xe,Ae,Be,He,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=ze,e.ɵa=F,e.ɵb=xe,e.ɵba=he,e.ɵbb=Ce,e.ɵbc=ve,e.ɵbd=Fe,e.ɵbe=se,e.ɵbf=ne,e.ɵbg=ae,e.ɵbh=oe,e.ɵbi=ie,e.ɵbj=le,e.ɵbk=Ae,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=Ve,e.ɵbs=Ee,e.ɵbt=Be,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=De,e.ɵcb=Ke,e.ɵcc=He,e.ɵcd=Ge,e.ɵce=Ue,e.ɵcf=je,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=me,e.ɵs=ue,e.ɵt=de,e.ɵu=pe,e.ɵv=ce,e.ɵw=fe,e.ɵx=ge,e.ɵy=ye,e.ɵz=be,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var y=function(e,t){return(y=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}y(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function h(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function C(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function v(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var F,x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-node-empty-config",template:"
"}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],i}return b(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),P=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(F||(F={}));var M,w=new Map([[F.CUSTOMER,"tb.rulenode.originator-customer"],[F.TENANT,"tb.rulenode.originator-tenant"],[F.RELATED,"tb.rulenode.originator-related"],[F.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,D=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,K=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var B,G=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(B||(B={}));var H,U,j,z=new Map([[B.TITLE,"tb.rulenode.entity-details-title"],[B.COUNTRY,"tb.rulenode.entity-details-country"],[B.STATE,"tb.rulenode.entity-details-state"],[B.ZIP,"tb.rulenode.entity-details-zip"],[B.ADDRESS,"tb.rulenode.entity-details-address"],[B.ADDRESS2,"tb.rulenode.entity-details-address2"],[B.PHONE,"tb.rulenode.entity-details-phone"],[B.EMAIL,"tb.rulenode.entity-details-email"],[B.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(H||(H={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var Q,_=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],W=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),J=["sas","cert.PEM"],Y=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Q||(Q={}));var Z=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],X=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ae=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return b(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=v(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",String)],r.prototype,"requiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyRequiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valRequiredText",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=h([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),C("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return b(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return b(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),le=function(e){function r(t,r,n,o){var i,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.truncate=n,s.fb=o,s.placeholder="tb.rulenode.message-type",s.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],s.messageTypes=[],s.messageTypesList=[],s.searchText="",s.propagateChange=function(e){},s.messageTypeConfigForm=s.fb.group({messageType:[null]});try{for(var u=v(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;s.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return s}var l;return b(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",String)],r.prototype,"label",void 0),h([t.Input(),C("design:type",Object)],r.prototype,"placeholder",void 0),h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.ViewChild("chipList",{static:!1}),C("design:type",d.MatChipList)],r.prototype,"chipList",void 0),h([t.ViewChild("messageTypeAutocomplete",{static:!1}),C("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),h([t.ViewChild("messageTypeInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=h([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),C("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=$,n.credentialsTypeTranslationsMap=W,n.propagateChange=null,n}var n;return b(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=v(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=h([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),me=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return this.sanitizer.bypassSecurityTrustHtml(e)},e.ctorParameters=function(){return[{type:g.DomSanitizer}]},e=h([t.Pipe({name:"safeHtml"}),C("design:paramtypes",[g.DomSanitizer])],e)}(),ue=function(){function e(){}return e=h([t.NgModule({declarations:[ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ae,oe,ie,le,se,me]})],e)}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=_,n}return b(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Z,n.ToByteStandartCharsetTypeTranslationMap=X,n}return b(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return b(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(Q),n}return b(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return b(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return b(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=J,n.azureIotHubCredentialsTypeTranslationsMap=Y,n}return b(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.sendSmsConfigForm},r.prototype.onConfigurationSet=function(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[i.Validators.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[i.Validators.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmsSettings"]},r.prototype.updateValidators=function(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([i.Validators.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-send-sms-config",template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(){function e(){}return e=h([t.NgModule({declarations:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,ue],exports:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te]})],e)}(),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return b(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n}return b(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return b(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=v(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return b(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},h([t.ViewChild("alarmStatusInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=h([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=h([t.NgModule({declarations:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le],imports:[r.CommonModule,a.SharedModule,ue],exports:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le]})],e)}(),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=z,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=v(Object.keys(B)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(B[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return b(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(z.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(z.get(B[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},h([t.ViewChild("detailsInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=h([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n.fetchMode=H,n.fetchModes=Object.keys(H),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===H.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),He=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.calculateDeltaConfigForm},r.prototype.onConfigurationSet=function(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[i.Validators.required]],outputValueKey:[e?e.outputValueKey:null,[i.Validators.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[i.Validators.min(0),i.Validators.max(15)]],tellFailureIfInputValueKeyIsAbsent:[e?e.tellFailureIfInputValueKeyIsAbsent:null,[]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})},r.prototype.updateValidators=function(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([i.Validators.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})},r.prototype.validatorTriggers=function(){return["addPeriodBetweenMsgs"]},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-calculate-delta-config",template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-input-value-key-is-absent\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(){function e(){}return e=h([t.NgModule({declarations:[Me,we,Re,De,Oe,Ke,Be,Ge,He],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,Ge,He]})],e)}(),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=F,n.originatorSources=Object.keys(F),n.originatorSourceTranslationMap=w,n}return b(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===F.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ze=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return b(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:s.NodeScriptTestService},{type:n.TranslateService}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),_e=function(){function e(){}return e=h([t.NgModule({declarations:[je,ze,Qe],imports:[r.CommonModule,a.SharedModule,ue],exports:[je,ze,Qe]})],e)}(),$e=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-input-value-key-is-absent":"Tell Failure if input value key is absent","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=h([t.NgModule({declarations:[x],imports:[r.CommonModule,a.SharedModule],exports:[qe,Pe,Ue,_e,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=$e,e.ɵa=x,e.ɵb=qe,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=ue,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=le,e.ɵbj=se,e.ɵbk=me,e.ɵbl=Pe,e.ɵbm=Se,e.ɵbn=Ie,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Ue,e.ɵbv=Me,e.ɵbw=we,e.ɵbx=Re,e.ɵby=De,e.ɵbz=Oe,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=Ge,e.ɵcd=He,e.ɵce=_e,e.ɵcf=je,e.ɵcg=ze,e.ɵch=Qe,e.ɵd=q,e.ɵe=S,e.ɵf=I,e.ɵg=k,e.ɵh=N,e.ɵi=V,e.ɵj=E,e.ɵk=A,e.ɵl=L,e.ɵm=P,e.ɵn=ee,e.ɵo=te,e.ɵp=re,e.ɵq=ne,e.ɵr=de,e.ɵs=pe,e.ɵt=ce,e.ɵu=fe,e.ɵv=ge,e.ɵw=ye,e.ɵx=be,e.ɵy=he,e.ɵz=Ce,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index cc47cbe089..cded6530f5 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -27,7 +27,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; @@ -35,7 +35,6 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -55,7 +54,8 @@ import java.util.function.Consumer; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.anyLong; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; @@ -294,7 +294,7 @@ public class TbAlarmNodeTest { when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm)); - when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())) + when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), nullable(JsonNode.class), anyLong())) .thenReturn(Futures.immediateFuture( false)); when(alarmService.findAlarmByIdAsync(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()))).thenReturn(Futures.immediateFuture(activeAlarm)); // doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm); @@ -346,7 +346,7 @@ public class TbAlarmNodeTest { when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); when(alarmService.findAlarmByIdAsync(tenantId, id)).thenReturn(Futures.immediateFuture(activeAlarm)); - when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true)); + when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), nullable(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true)); // doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm); node.onMsg(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 0c0f49f298..0f5dfb6ee7 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -22,10 +22,9 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -66,7 +65,6 @@ public class TbJsFilterNodeTest { public void falseEvaluationDoNotSendMsg() throws TbNodeException, ScriptException { initWithScript(); TbMsg msg = TbMsg.newMsg("USER", null, new TbMsgMetaData(), TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(false)); node.onMsg(ctx, msg); @@ -79,7 +77,6 @@ public class TbJsFilterNodeTest { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFailedFuture(new ScriptException("error"))); @@ -92,7 +89,6 @@ public class TbJsFilterNodeTest { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeFilterAsync(msg)).thenReturn(Futures.immediateFuture(true)); node.onMsg(ctx, msg); @@ -112,18 +108,6 @@ public class TbJsFilterNodeTest { node.init(ctx, nodeConfiguration); } - private void mockJsExecutor() { - when(ctx.getJsExecutor()).thenReturn(executor); - doAnswer((Answer>) invocationOnMock -> { - try { - Callable task = (Callable) (invocationOnMock.getArguments())[0]; - return Futures.immediateFuture((Boolean) task.call()); - } catch (Throwable th) { - return Futures.immediateFailedFuture(th); - } - }).when(executor).executeAsync(Matchers.any(Callable.class)); - } - private void verifyError(TbMsg msg, String message, Class expectedClass) { ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); verify(ctx).tellFailure(same(msg), captor.capture()); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index 5609345ac8..cfacf4cb85 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -23,9 +23,9 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -43,7 +43,7 @@ import java.util.Set; import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.same; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -92,6 +92,7 @@ public class TbJsSwitchNodeTest { node.init(ctx, nodeConfiguration); } + @SuppressWarnings("unchecked") private void mockJsExecutor() { when(ctx.getJsExecutor()).thenReturn(executor); doAnswer((Answer>>) invocationOnMock -> { @@ -101,7 +102,7 @@ public class TbJsSwitchNodeTest { } catch (Throwable th) { return Futures.immediateFailedFuture(th); } - }).when(executor).executeAsync(Matchers.any(Callable.class)); + }).when(executor).executeAsync(ArgumentMatchers.any(Callable.class)); } private void verifyError(TbMsg msg, String message, Class expectedClass) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index 4ded096fb5..3385d20d3a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -21,7 +21,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index 1d811b4ef9..ca88290808 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -58,13 +58,12 @@ import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @RunWith(MockitoJUnitRunner.class) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index ef8c1c5558..3cdac17b42 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -18,26 +18,34 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalAnswers; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.device.profile.AlarmCondition; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyValueType; @@ -48,12 +56,19 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; +import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.Collections; +import java.util.List; +import java.util.Optional; import java.util.TreeMap; import java.util.UUID; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) @@ -71,9 +86,14 @@ public class TbDeviceProfileNodeTest { private TimeseriesService timeseriesService; @Mock private RuleEngineAlarmService alarmService; + @Mock + private DeviceService deviceService; + @Mock + private AttributesService attributesService; private TenantId tenantId = new TenantId(UUID.randomUUID()); private DeviceId deviceId = new DeviceId(UUID.randomUUID()); + private CustomerId customerId = new CustomerId(UUID.randomUUID()); private DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); @Test @@ -183,11 +203,389 @@ public class TbDeviceProfileNodeTest { } + @Test + public void testCurrentDeviceAttributeForDynamicValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + ListenableFuture> listListenableFutureWithLess = + Futures.immediateFuture(Collections.singletonList(entry)); + + KeyFilter highTempFilter = new KeyFilter(); + highTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute") + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFutureWithLess); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentCustomersAttributeForDynamicValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + ListenableFuture> listListenableFutureWithLess = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> optionalListenableFutureWithLess = + Futures.immediateFuture(Optional.of(entry)); + + KeyFilter lowTempFilter = new KeyFilter(); + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); + lowTempPredicate.setValue( + new FilterPredicateValue<>( + 20.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "lessAttribute")) + ); + lowTempFilter.setPredicate(lowTempPredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("lesstempID"); + dpa.setAlarmType("lessTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFutureWithLess); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(optionalListenableFutureWithLess); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 25); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentTenantAttributeForDynamicValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "lessAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(50L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + ListenableFuture> listListenableFutureWithLess = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> optionalListenableFutureWithLess = + Futures.immediateFuture(Optional.of(entry)); + + KeyFilter lowTempFilter = new KeyFilter(); + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.LESS); + lowTempPredicate.setValue( + new FilterPredicateValue<>( + 32.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "lessAttribute")) + ); + lowTempFilter.setPredicate(lowTempPredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("lesstempID"); + dpa.setAlarmType("lessTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) + .thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFutureWithLess); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(optionalListenableFutureWithLess); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 40); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testTenantInheritModeForDynamicValues() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "tenantAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(100L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + ListenableFuture> listListenableFutureWithLess = + Futures.immediateFuture(Collections.singletonList(entry)); + + KeyFilter lowTempFilter = new KeyFilter(); + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + lowTempPredicate.setValue( + new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "tenantAttribute", true)) + ); + lowTempFilter.setPredicate(lowTempPredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("lesstempID"); + dpa.setAlarmType("lessTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) + .thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFutureWithLess); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 150L); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + + } + + @Test + public void testCustomerInheritModeForDynamicValues() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "customerAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(100L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + ListenableFuture> listListenableFutureWithLess = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> optionalListenableFutureWithLess = + Futures.immediateFuture(Optional.of(entry)); + + KeyFilter lowTempFilter = new KeyFilter(); + lowTempFilter.setKey(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + lowTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate lowTempPredicate = new NumericFilterPredicate(); + lowTempPredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + lowTempPredicate.setValue( + new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_CUSTOMER, "customerAttribute", true)) + ); + lowTempFilter.setPredicate(lowTempPredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(lowTempFilter)); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("lesstempID"); + dpa.setAlarmType("lessTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "lessTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())) + .thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFutureWithLess); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(optionalListenableFutureWithLess); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + .thenReturn(theMsg); + + ObjectNode data = mapper.createObjectNode(); + data.put("temperature", 150L); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + private void init() throws TbNodeException { Mockito.when(ctx.getTenantId()).thenReturn(tenantId); Mockito.when(ctx.getDeviceProfileCache()).thenReturn(cache); Mockito.when(ctx.getTimeseriesService()).thenReturn(timeseriesService); Mockito.when(ctx.getAlarmService()).thenReturn(alarmService); + Mockito.when(ctx.getDeviceService()).thenReturn(deviceService); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.createObjectNode()); node = new TbDeviceProfileNode(); node.init(ctx, nodeConfiguration); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index 772c17d790..10202e2a68 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -43,9 +43,9 @@ import org.thingsboard.server.dao.asset.AssetService; import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 62558f25d1..ca6a4f8c02 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -22,9 +22,9 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -42,12 +42,10 @@ import java.util.Collections; import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; -import static org.mockito.Matchers.same; +import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @RunWith(MockitoJUnitRunner.class) public class TbTransformMsgNodeTest { @@ -72,7 +70,6 @@ public class TbTransformMsgNodeTest { RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON,rawJson, ruleChainId, ruleNodeId); TbMsg transformedMsg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, "{new}", ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFuture(Collections.singletonList(transformedMsg))); node.onMsg(ctx, msg); @@ -93,7 +90,6 @@ public class TbTransformMsgNodeTest { RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeUpdateAsync(msg)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("error"))); node.onMsg(ctx, msg); @@ -112,18 +108,6 @@ public class TbTransformMsgNodeTest { node.init(ctx, nodeConfiguration); } - private void mockJsExecutor() { - when(ctx.getJsExecutor()).thenReturn(executor); - doAnswer((Answer>) invocationOnMock -> { - try { - Callable task = (Callable) (invocationOnMock.getArguments())[0]; - return Futures.immediateFuture((TbMsg) task.call()); - } catch (Throwable th) { - return Futures.immediateFailedFuture(th); - } - }).when(executor).executeAsync(Matchers.any(Callable.class)); - } - private void verifyError(TbMsg msg, String message, Class expectedClass) { ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); verify(ctx).tellFailure(same(msg), captor.capture()); diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index 440be0ecea..1ede01a491 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -76,7 +76,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/transport/coap/src/main/conf/tb-coap-transport.conf b/transport/coap/src/main/conf/tb-coap-transport.conf index 0f8db67300..73b6e04f68 100644 --- a/transport/coap/src/main/conf/tb-coap-transport.conf +++ b/transport/coap/src/main/conf/tb-coap-transport.conf @@ -14,10 +14,9 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/http/pom.xml b/transport/http/pom.xml index bb9262de66..41ca5683a1 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -76,7 +76,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/transport/http/src/main/conf/tb-http-transport.conf b/transport/http/src/main/conf/tb-http-transport.conf index 0f8db67300..73b6e04f68 100644 --- a/transport/http/src/main/conf/tb-http-transport.conf +++ b/transport/http/src/main/conf/tb-http-transport.conf @@ -14,10 +14,9 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index e75c5212d7..27dad89bb3 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -98,7 +98,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/transport/lwm2m/src/main/conf/tb-lwm2m-transport.conf b/transport/lwm2m/src/main/conf/tb-lwm2m-transport.conf index 0f8db67300..73b6e04f68 100644 --- a/transport/lwm2m/src/main/conf/tb-lwm2m-transport.conf +++ b/transport/lwm2m/src/main/conf/tb-lwm2m-transport.conf @@ -14,10 +14,9 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks b/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks index 9336dd1168..9f6748f8fd 100644 Binary files a/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks and b/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks differ diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh index 3167cca2b5..dcf38b4f10 100755 --- a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_credentials.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh # # Copyright © 2016-2021 The Thingsboard Authors # @@ -15,77 +15,35 @@ # limitations under the License. # -#p) CLIENT_CN=LwX50900000000 +#/home/nick/Igor_project/Thingsboard_Perfrmance_test/performance-tests/src/main/resources/credentials/shell/lwM2M_credentials.sh -p LwX509 -s 0 -f 2000 -a client_alias_ -e client_self_signed_ -b bootstrap -d server -j serverKeyStore.jks -k clientKeyStore.jks -c client_ks_password -w server_ks_password + +#p) CLIENT_CN=$CLIENT_PREFIX00000000 #s) client_start=0 #f) client_finish=1 -#a) CLIENT_ALIAS=client_alias_00000000 +#a) CLIENT_ALIAS=CLIENT_ALIAS_PREFIX_00000000 +#e) CLIENT_SELF_ALIAS=CLIENT_SELF_ALIAS_PREFIX_00000000 #b) BOOTSTRAP_ALIAS=bootstrap #d) SERVER_ALIAS=server #j) SERVER_STORE=serverKeyStore.jks #k) CLIENT_STORE=clientKeyStore.jks #c) CLIENT_STORE_PWD=client_ks_password #w) SERVER_STORE_PWD=server_ks_password +#l) ROOT_KEY_ALIAS=root_key_alias -#while test $# -gt 0; do -# case "$1" in -# -h|--help) -# echo "$package - attempt to capture frames" -# echo " " -# echo "$package [options] application [arguments]" -# echo " " -# echo "options:" -# echo "-h, --help show brief help" -# echo "-a, --action=ACTION specify an action to use" -# echo "-o, --output-dir=DIR specify a directory to store output in" -# exit 0 -# ;; -# -a) -# shift -# if test $# -gt 0; then -# export PROCESS=$1 -# else -# echo "no process specified" -# exit 1 -# fi -# shift -# ;; -# --action*) -# export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` -# shift -# ;; -# -o) -# shift -# if test $# -gt 0; then -# export OUTPUT=$1 -# else -# echo "no output dir specified" -# exit 1 -# fi -# shift -# ;; -# --output-dir*) -# export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` -# shift -# ;; -# *) -# break -# ;; -# esac -#done - - -while getopts p:s:f:a:b:d:j:k:c:w: flag; do +while getopts p:s:f:a:e:b:d:j:k:c:w:l: flag; do case "${flag}" in - p) client_prefix=${OPTARG} ;; + p) client_pref=${OPTARG} ;; s) client_start=${OPTARG} ;; f) client_finish=${OPTARG} ;; - a) client_alias=${OPTARG} ;; + a) client_alias_pref=${OPTARG} ;; + e) client_self_alias_pref=${OPTARG} ;; b) bootstrap_alias=${OPTARG} ;; d) server_alias=${OPTARG} ;; j) key_store_server_file=${OPTARG} ;; k) key_store_client_file=${OPTARG} ;; c) client_key_store_pwd=${OPTARG} ;; w) server_key_store_pwd=${OPTARG} ;; + w) root_key_alias=${OPTARG} ;; esac done @@ -96,9 +54,8 @@ cd $script_dir # source the properties: . ./lwM2M_keygen.properties - -if [ -n "$client_prefix" ]; then - CLIENT_PREFIX=$client_prefix +if [ -n "$client_pref" ]; then + CLIENT_PREFIX=$client_pref fi if [ -z "$client_start" ]; then @@ -109,8 +66,12 @@ if [ -z "$client_finish" ]; then client_finish=1 fi -if [ -n "$client_alias" ]; then - CLIENT_ALIAS=$client_alias +if [ -n "$client_alias_pref" ]; then + CLIENT_ALIAS_PREFIX=$client_alias_pref +fi + +if [ -n "$client_self_alias_pref" ]; then + CLIENT_SELF_ALIAS_PREFIX=$client_self_alias_pref fi if [ -n "$bootstrap_alias" ]; then @@ -137,23 +98,37 @@ if [ -n "$server_key_store_pwd" ]; then SERVER_STORE_PWD=$server_key_store_pwd fi +if [ -n "$root_key_alias" ]; then + ROOT_KEY_ALIAS=$root_key_alias +fi + +CLIENT_NUMBER=$client_start + echo "==Start==" echo "CLIENT_PREFIX: $CLIENT_PREFIX" echo "client_start: $client_start" echo "client_finish: $client_finish" -echo "CLIENT_ALIAS: $CLIENT_ALIAS" +echo "CLIENT_ALIAS_PREFIX: $CLIENT_ALIAS_PREFIX" +echo "CLIENT_SELF_ALIAS_PREFIX: $CLIENT_SELF_ALIAS_PREFIX" echo "BOOTSTRAP_ALIAS: $BOOTSTRAP_ALIAS" echo "SERVER_ALIAS: $SERVER_ALIAS" echo "SERVER_STORE: $SERVER_STORE" echo "CLIENT_STORE: $CLIENT_STORE" echo "CLIENT_STORE_PWD: $CLIENT_STORE_PWD" echo "SERVER_STORE_PWD: $SERVER_STORE_PWD" +echo "CLIENT_NUMBER: $CLIENT_NUMBER" +echo "ROOT_KEY_ALIAS: $ROOT_KEY_ALIAS" end_point() { echo "$CLIENT_PREFIX$(printf "%08d" $CLIENT_NUMBER)" } + client_alias_point() { - echo "$CLIENT_ALIAS$(printf "%08d" $CLIENT_NUMBER)" + echo "$CLIENT_ALIAS_PREFIX$(printf "%08d" $CLIENT_NUMBER)" +} + +client_self_alias_point() { + echo "$CLIENT_SELF_ALIAS_PREFIX$(printf "%08d" $CLIENT_NUMBER)" } # Generation of the keystore. @@ -264,73 +239,30 @@ keytool \ -keystore $SERVER_STORE \ -storepass $SERVER_STORE_PWD -echo -echo "${H1}Client Keystore : ${RESET}" -echo "${H1}==================${RESET}" -#echo "${H2}Creating client key and self-signed certificate with expected CN...${RESET}" -#keytool \ -# -genkeypair \ -# -alias $CLIENT_ALIAS \ -# -keyalg EC \ -# -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -# -validity $VALIDITY \ -# -storetype $STORETYPE \ -# -keypass $CLIENT_STORE_PWD \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD -#keytool \ -# -exportcert \ -# -alias $CLIENT_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD | \ -# keytool \ -# -importcert \ -# -alias $CLIENT_SELF_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD \ -# -noprompt - -echo -echo "${H2}Import root certificate just to be able to import need by root CA with expected CN...${RESET}" -keytool \ - -exportcert \ - -alias $ROOT_KEY_ALIAS \ - -keystore $SERVER_STORE \ - -storepass $SERVER_STORE_PWD | +if [ "$client_start" -lt "$client_finish" ]; then + echo + echo "${H2}Import root certificate just to be able to import need by root CA with expected CN to $CLIENT_STORE${RESET}" keytool \ - -importcert \ + -exportcert \ -alias $ROOT_KEY_ALIAS \ - -keystore $CLIENT_STORE \ - -storepass $CLIENT_STORE_PWD \ - -noprompt - -#echo -#echo "${H2}Creating client certificate signed by root CA with expected CN...${RESET}" -#keytool \ -# -certreq \ -# -alias $CLIENT_ALIAS \ -# -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD | \ -# keytool \ -# -gencert \ -# -alias $ROOT_KEY_ALIAS \ -# -keystore $SERVER_STORE \ -# -storepass $SERVER_STORE_PWD \ -# -storetype $STORETYPE \ -# -validity $VALIDITY | \ -# keytool \ -# -importcert \ -# -alias $CLIENT_ALIAS \ -# -keystore $CLIENT_STORE \ -# -storepass $CLIENT_STORE_PWD \ -# -noprompt + -keystore $SERVER_STORE \ + -storepass $SERVER_STORE_PWD | + keytool \ + -importcert \ + -alias $ROOT_KEY_ALIAS \ + -keystore $CLIENT_STORE \ + -storepass $CLIENT_STORE_PWD \ + -noprompt +fi cert_end_point() { - echo "${H2}Creating client key and self-signed certificate with expected CN $CLIENT_SELF_CN ${RESET}" + echo + echo "${H1}Client Keystore : ${RESET}" + echo "${H1}==================${RESET}" + echo "${H2}Creating client key and self-signed certificate with expected CN CLIENT_ALIAS: $CLIENT_ALIAS${RESET}" keytool \ -genkeypair \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keyalg EC \ -dname "CN=$CLIENT_SELF_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -validity $VALIDITY \ @@ -340,7 +272,7 @@ cert_end_point() { -storepass $CLIENT_STORE_PWD keytool \ -exportcert \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD | keytool \ @@ -349,13 +281,28 @@ cert_end_point() { -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD \ -noprompt +# +# echo +# echo "${H2}Import root certificate just to be able to import ned by root CA with expected CN...${RESET}" +# keytool \ +# -exportcert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $SERVER_STORE \ +# -storepass $SERVER_STORE_PWD | +# keytool \ +# -importcert \ +# -alias $ROOT_KEY_ALIAS \ +# -keystore $CLIENT_STORE \ +# -storepass $CLIENT_STORE_PWD \ +# -noprompt +# echo - echo "${H2}Creating client certificate signed by root CA with expected $CLIENT_CN_NAME ${RESET}" + echo "${H2}Creating client certificate signed by root CA with expected CN CLIENT_ALIAS: $CLIENT_ALIAS CLIENT_CN: $CLIENT_CN${RESET}" keytool \ -certreq \ - -alias $CLIENT_CN_ALIAS \ - -dname "CN=$CLIENT_CN_NAME, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ + -alias $CLIENT_ALIAS \ + -dname "CN=$CLIENT_CN, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD | keytool \ @@ -367,22 +314,30 @@ cert_end_point() { -validity $VALIDITY | keytool \ -importcert \ - -alias $CLIENT_CN_ALIAS \ + -alias $CLIENT_ALIAS \ -keystore $CLIENT_STORE \ -storepass $CLIENT_STORE_PWD \ -noprompt } -while [ "$CLIENT_NUMBER" != "$client_finish" ]; do - CLIENT_CN_NAME=$(end_point) - CLIENT_CN_ALIAS=$(client_alias_point) - echo "$CLIENT_CN_NAME" - echo "$CLIENT_CN_ALIAS" - cert_end_point - CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) - echo "number $CLIENT_NUMBER" - echo "finish $client_finish" -done +if [ "$client_start" -lt "$client_finish" ]; then + echo "Файл содержит, как минимум, одно слово Bash." + echo + echo "==Start Client==" + while [ "$CLIENT_NUMBER" -lt "$client_finish" ]; do + echo "number $CLIENT_NUMBER" + echo "finish $client_finish" + CLIENT_CN=$(end_point) + CLIENT_ALIAS=$(client_alias_point) + CLIENT_SELF_ALIAS=$(client_self_alias_point) + echo "CLIENT_CN $CLIENT_CN" + echo "CLIENT_ALIAS $CLIENT_ALIAS" + echo "CLIENT_SELF_ALIAS $CLIENT_SELF_ALIAS" + cert_end_point + CLIENT_NUMBER=$(($CLIENT_NUMBER + 1)) + echo + done +fi echo echo "${H0}!!! Warning ${H2}Migrate ${H1}${SERVER_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" @@ -393,11 +348,13 @@ keytool \ -deststoretype pkcs12 \ -srcstorepass $SERVER_STORE_PWD -echo -echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" -keytool \ - -importkeystore \ - -srckeystore $CLIENT_STORE \ - -destkeystore $CLIENT_STORE \ - -deststoretype pkcs12 \ - -srcstorepass $CLIENT_STORE_PWD +if [ "$client_start" -lt "$client_finish" ]; then + echo + echo "${H0}!!! Warning ${H2}Migrate ${H1}${CLIENT_STORE} ${H2}to ${H1}PKCS12 ${H2}which is an industry standard format..${RESET}" + keytool \ + -importkeystore \ + -srckeystore $CLIENT_STORE \ + -destkeystore $CLIENT_STORE \ + -deststoretype pkcs12 \ + -srcstorepass $CLIENT_STORE_PWD +fi diff --git a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties index 18f851e4dd..7b3cd9c09a 100644 --- a/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties +++ b/transport/lwm2m/src/main/data/credentials/shell/lwM2M_keygen.properties @@ -41,9 +41,9 @@ BOOTSTRAP_SELF_CN="$DOMAIN_SUFFIX bootstrap server LwM2M self-signed" # Client CLIENT_STORE=clientKeyStore1.jks CLIENT_STORE_PWD=client_ks_password1 -CLIENT_ALIAS=client_alias_1 -CLIENT_PREFIX=LwX509_ -CLIENT_SELF_ALIAS=client_self_signed +CLIENT_ALIAS_PREFIX=client_alias_1 +CLIENT_PREFIX=LwX509___ +CLIENT_SELF_ALIAS_PREFIX=client_self_signed_1 CLIENT_SELF_CN="$DOMAIN_SUFFIX client LwM2M self-signed" # Color output stuff diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 700b428906..3c6c9b2afa 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -53,14 +53,14 @@ transport: # model_path_file: "${LWM2M_MODEL_PATH_FILE:./common/transport/lwm2m/src/main/resources/models/}" model_path_file: "${LWM2M_MODEL_PATH_FILE:}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" - recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" request_pool_size: "${LWM2M_REQUEST_POOL_SIZE:100}" request_error_pool_size: "${LWM2M_REQUEST_ERROR_POOL_SIZE:10}" registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" secure: - # Only Certificate_x509: + # Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" @@ -69,51 +69,42 @@ transport: key_store_path_file: "${KEY_STORE_PATH_FILE:}" key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}" root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" - enable_gen_psk_rpk: "${ENABLE_GEN_PSK_RPK:true}" + enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}" server: id: "${LWM2M_SERVER_ID:123}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_PSK:5685}" - bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_RPK:5687}" - bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_X509:5689}" + bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}" secure: - bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - start_psk: "${START_SERVER_PSK:true}" - start_rpk: "${START_SERVER_RPK:true}" - start_x509: "${START_SERVER_X509:true}" - bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK:5686}" - bind_port_rpk: "${LWM2M_BIND_PORT_SEC_RPK:5688}" - bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509:5690}" - # Only RPK: Public & Private Key - # create_rpk: "${CREATE_RPK:}" - public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" - public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}" - private_s: "${LWM2M_SERVER_PRIVATE_S:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}" - # Only Certificate_x509: + bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" + bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}" + # create_rpk: "${CREATE_RPK:}" + # Only for RPK: Public & Private Key. If the keystore file is missing or not working + # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d0301070342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b] + # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b], + # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] + public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" + public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" + private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" bootstrap: - enable: "${BOOTSTRAP:true}" + enable: "${LWM2M_BOOTSTRAP_ENABLED:true}" id: "${LWM2M_SERVER_ID:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port_no_sec_psk: "${LWM2M_BIND_PORT_NO_SEC_BS:5691}" - bind_port_no_sec_rpk: "${LWM2M_BIND_PORT_NO_SEC_BS:5693}" - bind_port_no_sec_x509: "${LWM2M_BIND_PORT_NO_SEC_BS:5695}" + bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}" secure: - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - start_psk: "${START_SERVER_PSK_BS:true}" - start_rpk: "${START_SERVER_RPK_BS:true}" - start_x509: "${START_SERVER_X509_BS:true}" - bind_port_psk: "${LWM2M_BIND_PORT_SEC_PSK_BS:5692}" - bind_port_rpk: "${LWM2M_BIND_PORT_SER_RPK_BS:5694}" - bind_port_x509: "${LWM2M_BIND_PORT_SEC_X509_BS:5696}" - # Only RPK: Public & Private Key - public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" - public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" - private_s: "${LWM2M_SERVER_PRIVATE_S_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" - # Only Certificate_x509: + bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" + bind_port_security: "${LWM2M_BIND_PORT_SEC_BS:5688}" + # Only for RPK: Public & Private Key. If the keystore file is missing or not working + # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] + # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34] + # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34], + public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" + public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" + private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_BOOTSTRAP:bootstrap}" - # Redis + # Redis redis_url: "${LWM2M_REDIS_URL:''}" + sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 579d514729..28aaeeab39 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -76,7 +76,7 @@ org.mockito - mockito-all + mockito-core test diff --git a/transport/mqtt/src/main/conf/tb-mqtt-transport.conf b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf index 0f8db67300..73b6e04f68 100644 --- a/transport/mqtt/src/main/conf/tb-mqtt-transport.conf +++ b/transport/mqtt/src/main/conf/tb-mqtt-transport.conf @@ -14,10 +14,9 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" -export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" -export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" -export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" -export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly" +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf diff --git a/ui-ngx/src/app/core/api/alias-controller.ts b/ui-ngx/src/app/core/api/alias-controller.ts index 8b2c8f0078..6ca208c506 100644 --- a/ui-ngx/src/app/core/api/alias-controller.ts +++ b/ui-ngx/src/app/core/api/alias-controller.ts @@ -53,8 +53,8 @@ export class AliasController implements IAliasController { private stateControllerHolder: StateControllerHolder, private origEntityAliases: EntityAliases, private origFilters: Filters) { - this.entityAliases = deepClone(this.origEntityAliases); - this.filters = deepClone(this.origFilters); + this.entityAliases = deepClone(this.origEntityAliases) || {}; + this.filters = deepClone(this.origFilters) || {}; this.userFilters = {}; } diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 2ae6473d54..782702dc11 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -21,11 +21,13 @@ export interface SysParamsState { allowedDashboardIds: string[]; edgesSupportEnabled: boolean; } + export interface AuthPayload extends SysParamsState { authUser: AuthUser; userDetails: User; forceFullscreen: boolean; } + export interface AuthState extends AuthPayload { isAuthenticated: boolean; isUserLoaded: boolean; diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index 06a76e62e1..fdba07b94a 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -15,7 +15,7 @@ /// import { Injectable, NgZone } from '@angular/core'; -import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, RouterStateSnapshot } from '@angular/router'; +import { ActivatedRouteSnapshot, CanActivate, CanActivateChild, Router, RouterStateSnapshot } from '@angular/router'; import { AuthService } from '../auth/auth.service'; import { select, Store } from '@ngrx/store'; import { AppState } from '../core.state'; @@ -28,6 +28,7 @@ import { Authority } from '@shared/models/authority.enum'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; +import { isObject } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -35,6 +36,7 @@ import { UtilsService } from '@core/services/utils.service'; export class AuthGuard implements CanActivate, CanActivateChild { constructor(private store: Store, + private router: Router, private authService: AuthService, private dialogService: DialogService, private utils: UtilsService, @@ -115,6 +117,14 @@ export class AuthGuard implements CanActivate, CanActivateChild { if (data.auth && data.auth.indexOf(authority) === -1) { this.dialogService.forbidden(); return of(false); + } else if (data.redirectTo) { + let redirect; + if (isObject(data.redirectTo)) { + redirect = data.redirectTo[authority]; + } else { + redirect = data.redirectTo; + } + return of(this.router.parseUrl(redirect)); } else { return of(true); } diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 4c6d1ca73b..bf7042e985 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -20,7 +20,7 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { PageData } from '@shared/models/page/page-data'; -import { Dashboard, DashboardInfo } from '@shared/models/dashboard.models'; +import { Dashboard, DashboardInfo, HomeDashboard, HomeDashboardInfo } from '@shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; import { NavigationEnd, Router } from '@angular/router'; import { filter, map, publishReplay, refCount } from 'rxjs/operators'; @@ -122,6 +122,19 @@ export class DashboardService { defaultHttpOptionsFromConfig(config)); } + public getHomeDashboard(config?: RequestConfig): Observable { + return this.http.get('/api/dashboard/home', defaultHttpOptionsFromConfig(config)); + } + + public getTenantHomeDashboardInfo(config?: RequestConfig): Observable { + return this.http.get('/api/tenant/dashboard/home/info', defaultHttpOptionsFromConfig(config)); + } + + public setTenantHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo, config?: RequestConfig): Observable { + return this.http.post('/api/tenant/dashboard/home/info', homeDashboardInfo, + defaultHttpOptionsFromConfig(config)); + } + public getPublicDashboardLink(dashboard: DashboardInfo): string | null { if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { const publicCustomers = dashboard.assignedCustomers diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index f669071e6d..e65e7017e0 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -295,9 +295,9 @@ export class RuleChainService { ); } - public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?:RequestConfig): Observable> { + public getEdgeRuleChains(edgeId: string, pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>(`/api/edge/${edgeId}/ruleChains${pageLink.toQuery()}`, - defaultHttpOptionsFromConfig(config) ) + defaultHttpOptionsFromConfig(config)); } public assignRuleChainToEdge(edgeId: string, ruleChainId: string, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index c2566c2aae..85eda1037d 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -223,6 +223,7 @@ export class MenuService { name: 'home.home', type: 'link', path: '/home', + notExact: true, icon: 'home' }, { @@ -312,6 +313,13 @@ export class MenuService { path: '/dashboards', icon: 'dashboards' }, + { + id: guid(), + name: 'admin.home-settings', + type: 'link', + path: '/settings/home', + icon: 'settings_applications' + }, { id: guid(), name: 'audit-log.audit-logs', @@ -453,6 +461,7 @@ export class MenuService { name: 'home.home', type: 'link', path: '/home', + notExact: true, icon: 'home' }, { diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index db12d8ff65..743b49c286 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -43,6 +43,7 @@ import { AlarmDetailsDialogComponent, AlarmDetailsDialogData } from '@home/components/alarm/alarm-details-dialog.component'; +import { DAY, historyInterval } from '@shared/models/time/time.models'; export class AlarmTableConfig extends EntityTableConfig { @@ -59,6 +60,7 @@ export class AlarmTableConfig extends EntityTableConfig this.loadDataOnInit = false; this.tableTitle = ''; this.useTimePageLink = true; + this.defaultTimewindowInterval = historyInterval(DAY * 30); this.detailsPanelEnabled = false; this.selectionEnabled = false; this.searchEnabled = true; diff --git a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts index ae6eeaadf9..ad7b3c6a11 100644 --- a/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/aliases-entity-select.component.ts @@ -14,14 +14,23 @@ /// limitations under the License. /// -import { Component, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { + Component, + Injector, + Input, + OnDestroy, + OnInit, + StaticProvider, + ViewChild, + ViewContainerRef +} from '@angular/core'; import { TooltipPosition } from '@angular/material/tooltip'; import { AliasInfo, IAliasController } from '@core/api/widget-api.models'; import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; import { TranslateService } from '@ngx-translate/core'; import { Subscription } from 'rxjs'; import { BreakpointObserver } from '@angular/cdk/layout'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { ALIASES_ENTITY_SELECT_PANEL_DATA, AliasesEntitySelectPanelComponent, @@ -136,12 +145,12 @@ export class AliasesEntitySelectComponent implements OnInit, OnDestroy { overlayRef.attach(new ComponentPortal(AliasesEntitySelectPanelComponent, this.viewContainerRef, injector)); } - private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): PortalInjector { - const injectionTokens = new WeakMap([ - [ALIASES_ENTITY_SELECT_PANEL_DATA, data], - [OverlayRef, overlayRef] - ]); - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + private _createAliasesEntitySelectPanelInjector(overlayRef: OverlayRef, data: AliasesEntitySelectPanelData): Injector { + const providers: StaticProvider[] = [ + {provide: ALIASES_ENTITY_SELECT_PANEL_DATA, useValue: data}, + {provide: OverlayRef, useValue: overlayRef} + ]; + return Injector.create({parent: this.viewContainerRef.injector, providers}); } private updateDisplayValue() { diff --git a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html index ee1fc9c96e..769c8a08af 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.html @@ -195,7 +195,8 @@ [length]="dataSource.total() | async" [pageIndex]="pageLink.page" [pageSize]="pageLink.pageSize" - [pageSizeOptions]="[10, 20, 30]"> + [pageSizeOptions]="[10, 20, 30]" + showFirstLastButtons> { overlayRef.dispose(); }); - const injectionTokens = new WeakMap([ - [EDIT_ATTRIBUTE_VALUE_PANEL_DATA, { - attributeValue: attribute.value - } as EditAttributeValuePanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const providers: StaticProvider[] = [ + { + provide: EDIT_ATTRIBUTE_VALUE_PANEL_DATA, + useValue: { + attributeValue: attribute.value + } as EditAttributeValuePanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); const componentRef = overlayRef.attach(new ComponentPortal(EditAttributeValuePanelComponent, this.viewContainerRef, injector)); componentRef.onDestroy(() => { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 97f5071955..77e6047d8a 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -49,10 +49,11 @@ @@ -78,7 +79,7 @@ (click)="isFullscreen = !isFullscreen"> {{ isFullscreen ? 'fullscreen_exit' : 'fullscreen' }} - - { if (this.embedded) { - data.dashboard = this.dashboard; + data.dashboard = this.dashboardUtils.validateAndUpdateDashboard(this.dashboard); + data.currentDashboardId = this.dashboard.id ? this.dashboard.id.id : null; data.widgetEditMode = false; data.singlePageMode = false; + } else { + data.currentDashboardId = this.route.snapshot.params.dashboardId; } this.init(data); this.runChangeDetection(); @@ -289,7 +295,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC this.reset(); - this.currentDashboardId = this.route.snapshot.params.dashboardId; + this.currentDashboardId = data.currentDashboardId; if (this.route.snapshot.params.customerId) { this.currentCustomerId = this.route.snapshot.params.customerId; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html index 43f9ca4a3f..a095dae071 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.html @@ -23,6 +23,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -34,6 +35,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -45,6 +47,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -56,6 +59,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" @@ -67,6 +71,7 @@ [widgetLayouts]="{}" [isEdit]="false" [isMobile]="true" + [disableWidgetInteraction]="true" [isEditActionEnabled]="false" [isExportActionEnabled]="false" [isRemoveActionEnabled]="false" diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html index 7f1097444f..061a4d5227 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html @@ -53,6 +53,7 @@ [mobileRowHeight]="layoutCtx.gridSettings.mobileRowHeight" [isMobile]="isMobile" [isMobileDisabled]="widgetEditMode" + [disableWidgetInteraction]="isEdit" [isEditActionEnabled]="isEdit" [isExportActionEnabled]="isEdit && !widgetEditMode" [isRemoveActionEnabled]="isEdit && !widgetEditMode" diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts index 61eaa6ae5c..cd54ec7558 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts @@ -88,6 +88,8 @@ export abstract class StateControllerComponent implements IStateControllerCompon currentState: string; + syncStateWithQueryParam: boolean; + private rxSubscriptions = new Array(); private inited = false; @@ -99,18 +101,20 @@ export abstract class StateControllerComponent implements IStateControllerCompon } ngOnInit(): void { - this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { - const dashboardId = this.route.snapshot.params.dashboardId || ''; - if (this.dashboardId === dashboardId) { - const newState = this.decodeStateParam(paramMap.get('state')); - if (this.currentState !== newState) { - this.currentState = newState; - if (this.inited) { - this.onStateChanged(); + if (this.syncStateWithQueryParam) { + this.rxSubscriptions.push(this.route.queryParamMap.subscribe((paramMap) => { + const dashboardId = this.route.snapshot.params.dashboardId || ''; + if (this.dashboardId === dashboardId) { + const newState = this.decodeStateParam(paramMap.get('state')); + if (this.currentState !== newState) { + this.currentState = newState; + if (this.inited) { + this.onStateChanged(); + } } } - } - })); + })); + } this.init(); this.inited = true; } @@ -124,16 +128,18 @@ export abstract class StateControllerComponent implements IStateControllerCompon protected updateStateParam(newState: string) { this.currentState = newState; - const queryParams: Params = { state: this.currentState }; - this.ngZone.run(() => { - this.router.navigate( - [], - { - relativeTo: this.route, - queryParams, - queryParamsHandling: 'merge', - }); - }); + if (this.syncStateWithQueryParam) { + const queryParams: Params = {state: this.currentState}; + this.ngZone.run(() => { + this.router.navigate( + [], + { + relativeTo: this.route, + queryParams, + queryParamsHandling: 'merge', + }); + }); + } } public openRightLayout(): void { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts index 730f31dd0b..6a21ac564f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.models.ts @@ -24,6 +24,7 @@ export interface IStateControllerComponent extends IStateController { stateControllerInstanceId: string; state: string; currentState: string; + syncStateWithQueryParam: boolean; isMobile: boolean; states: {[id: string]: DashboardState }; dashboardId: string; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts index e377f77fff..b6911ed9d8 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/states-component.directive.ts @@ -53,6 +53,9 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { @Input() currentState: string; + @Input() + syncStateWithQueryParam: boolean; + @Input() isMobile: boolean; @@ -89,6 +92,8 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { this.stateControllerComponent.state = this.state; } else if (propName === 'currentState') { this.stateControllerComponent.currentState = this.currentState; + } else if (propName === 'syncStateWithQueryParam') { + this.stateControllerComponent.syncStateWithQueryParam = this.syncStateWithQueryParam; } } } @@ -119,6 +124,7 @@ export class StatesComponentDirective implements OnInit, OnDestroy, OnChanges { this.stateControllerComponent.stateControllerInstanceId = stateControllerInstanceId; this.stateControllerComponent.state = this.state; this.stateControllerComponent.currentState = this.currentState; + this.stateControllerComponent.syncStateWithQueryParam = this.syncStateWithQueryParam; this.stateControllerComponent.isMobile = this.isMobile; this.stateControllerComponent.states = this.states; this.stateControllerComponent.dashboardId = this.dashboardId; diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html index b472ca54cc..bd675a0a9f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.html @@ -150,7 +150,7 @@ -
+
+ [pageSizeOptions]="pageSizeOptions" + showFirstLastButtons>
diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index dedaced162..da735c1567 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -55,11 +55,11 @@ import { EntityTypeTranslation } from '@shared/models/entity-type.models'; import { DialogService } from '@core/services/dialog.service'; import { AddEntityDialogComponent } from './add-entity-dialog.component'; import { AddEntityDialogData, EntityAction } from '@home/models/entity/entity-component.models'; -import { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; +import { HistoryWindowType, Timewindow } from '@shared/models/time/time.models'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { isDefined, isUndefined } from '@core/utils'; -import { HasUUID } from '../../../../shared/models/id/has-uuid'; +import { HasUUID } from '@shared/models/id/has-uuid'; @Component({ selector: 'tb-entities-table', @@ -202,7 +202,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; if (this.entitiesTableConfig.useTimePageLink) { - this.timewindow = historyInterval(DAY); + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval; const currentTime = Date.now(); this.pageLink = new TimePageLink(10, 0, null, sortOrder, currentTime - this.timewindow.history.timewindowMs, currentTime); @@ -446,7 +446,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn resetSortAndFilter(update: boolean = true, preserveTimewindow: boolean = false) { this.pageLink.textSearch = null; if (this.entitiesTableConfig.useTimePageLink && !preserveTimewindow) { - this.timewindow = historyInterval(DAY); + this.timewindow = this.entitiesTableConfig.defaultTimewindowInterval; } if (this.displayPagination) { this.paginator.pageIndex = 0; diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index 0af4689a18..1efcc9211e 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -44,7 +44,7 @@ import { EntityAction } from '@home/models/entity/entity-component.models'; import { Subscription } from 'rxjs'; import { MatTab, MatTabGroup } from '@angular/material/tabs'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; -import { deepClone } from '@core/utils'; +import { deepClone, mergeDeep } from '@core/utils'; @Component({ selector: 'tb-entity-details-panel', @@ -280,7 +280,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements OnInit saveEntity() { if (this.detailsForm.valid) { - const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()}; + const editingEntity = mergeDeep(this.editingEntity, this.entityComponent.entityFormValue()); this.entitiesTableConfig.saveEntity(editingEntity).subscribe( (entity) => { this.entity = entity; diff --git a/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html b/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html index 6194581625..253f7e2138 100644 --- a/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/boolean-filter-predicate.component.html @@ -16,7 +16,7 @@ -->
- + @@ -25,7 +25,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html index 41c04ea238..c6aee52976 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-list.component.html @@ -26,12 +26,12 @@
-
+
- +
diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html index adda4bea1a..dd1b92cf7f 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html @@ -47,7 +47,7 @@
-
+
@@ -68,6 +68,14 @@
filter.source-attribute
+
+ + {{ 'filter.inherit-owner' | translate}} + +
filter.source-attribute-not-set
+
- {{ translate.get('device-profile.lwm2m.valid-id-instance', { + {{ 'device-profile.lwm2m.valid-id-instance' | translate: { count: 2, instance: instanceId, min: instanceIdValueMin - }) | async }} + } }} - {{ translate.get('device-profile.lwm2m.valid-id-instance', { + {{ 'device-profile.lwm2m.valid-id-instance' | translate: { count: 1, instance: instanceId, max: instanceIdValueMax - }) | async }} + } }} - {{ translate.get('device-profile.lwm2m.valid-id-instance', { + {{ 'device-profile.lwm2m.valid-id-instance' | translate: { count: 0, instance: instanceId, max: instanceIdValueMax - }) | async }} + } }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts index d1f47d4d03..1a0ab08e11 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts @@ -19,7 +19,6 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER } from './profile-config.models'; -import { TranslateService } from '@ngx-translate/core'; import { DeviceProfileService } from '@core/http/device-profile.service'; @Component({ @@ -44,7 +43,6 @@ export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccesso private propagateChange = (v: any) => { }; constructor(private store: Store, - public translate: TranslateService, private deviceProfileService: DeviceProfileService, private fb: FormBuilder) { this.instancesListFormGroup = this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html index 609af715a8..09297c9400 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html @@ -44,7 +44,7 @@ - {{ translate.get('device-profile.lwm2m.no-objects-matching', {object: searchText}) | async }} + {{ 'device-profile.lwm2m.no-objects-matching' | translate: {object: searchText} }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 300f95f83f..12be3af48c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -22,7 +22,6 @@ import { AppState } from '@core/core.state'; import { Observable } from 'rxjs'; import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; import { ModelValue, ObjectLwM2M } from './profile-config.models'; -import { TranslateService } from '@ngx-translate/core'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { Direction } from '@shared/models/page/sort-order'; import { isDefined, isDefinedAndNotNull, isEmptyStr, isString } from '@core/utils'; @@ -72,7 +71,6 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } constructor(private store: Store, - public translate: TranslateService, private deviceProfileService: DeviceProfileService, private fb: FormBuilder) { this.lwm2mListFormGroup = this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts index 05a4e42d2f..51d7735dd7 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/profile-config.models.ts @@ -14,21 +14,20 @@ /// limitations under the License. /// -import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; - export const INSTANCES = 'instances'; export const RESOURCES = 'resources'; +export const CLIENT_LWM2M = 'clientLwM2M'; +export const CLIENT_LWM2M_SETTINGS = 'clientLwM2mSettings'; export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry'; export const OBSERVE = 'observe'; export const ATTRIBUTE = 'attribute'; export const TELEMETRY = 'telemetry'; export const KEY_NAME = 'keyName'; -export const CLIENT_LWM2M = 'clientLwM2M'; export const DEFAULT_ID_SERVER = 123; export const DEFAULT_ID_BOOTSTRAP = 111; export const DEFAULT_HOST_NAME = 'localhost'; export const DEFAULT_PORT_SERVER_NO_SEC = 5685; -export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5691; +export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5686; export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; export const DEFAULT_LIFE_TIME = 300; export const DEFAULT_MIN_PERIOD = 1; @@ -91,10 +90,16 @@ interface BootstrapSecurityConfig { } export interface ProfileConfigModels { - bootstrap: BootstrapSecurityConfig; + clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; + bootstrap: BootstrapSecurityConfig; + } +export interface ClientLwM2mSettings { + clientOnlyObserveAfterConnect: boolean; + clientUpdateValueAfterConnect: boolean; +} export interface ObservableAttributes { observe: string[]; attribute: string[]; @@ -141,15 +146,27 @@ function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecur }; } +function getDefaultProfileObserveAttrConfig(): ObservableAttributes { + return { + observe: [], + attribute: [], + telemetry: [], + keyName: {} + }; +} + +function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { + return { + clientOnlyObserveAfterConnect: true, + clientUpdateValueAfterConnect: false + }; +} + export function getDefaultProfileConfig(hostname?: any): ProfileConfigModels { return { - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME), - observeAttr: { - observe: [], - attribute: [], - telemetry: [], - keyName: {} - } + clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), + observeAttr: getDefaultProfileObserveAttrConfig(), + bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME) }; } diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index d8f25c0321..33678c88b3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -67,15 +67,13 @@
-
-
widget-action.target-dashboard
- -
+
widget-action.target-dashboard
+
-
- - {{ 'widget-action.set-entity-from-widget' | translate }} - - - alias.state-entity-parameter-name - - -
+ + {{ 'widget-action.set-entity-from-widget' | translate }} + + + alias.state-entity-parameter-name + +
{{ 'widget-action.open-in-separate-dialog' | translate }} -
+
widget-action.dialog-title diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html index 04bc5708b6..ca4f2a56b7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html @@ -27,7 +27,8 @@
- +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index d5ba2aca5b..d58ace0a6b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -19,9 +19,11 @@ import { Component, ElementRef, EventEmitter, + Injector, Input, NgZone, OnInit, + StaticProvider, ViewChild, ViewContainerRef } from '@angular/core'; @@ -64,7 +66,7 @@ import { widthStyle } from '@home/components/widget/lib/table-widget.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { DISPLAY_COLUMNS_PANEL_DATA, DisplayColumnsPanelComponent, @@ -452,20 +454,26 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, }; }); - const injectionTokens = new WeakMap([ - [DISPLAY_COLUMNS_PANEL_DATA, { - columns, - columnsUpdated: (newColumns) => { - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); - if (this.enableSelection) { - this.displayedColumns.unshift('select'); + const providers: StaticProvider[] = [ + { + provide: DISPLAY_COLUMNS_PANEL_DATA, + useValue: { + columns, + columnsUpdated: (newColumns) => { + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); + if (this.enableSelection) { + this.displayedColumns.unshift('select'); + } + this.displayedColumns.push('actions'); } - this.displayedColumns.push('actions'); - } - } as DisplayColumnsPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + } as DisplayColumnsPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, this.viewContainerRef, injector)); this.ctx.detectChanges(); @@ -492,15 +500,21 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, overlayRef.backdropClick().subscribe(() => { overlayRef.dispose(); }); - const injectionTokens = new WeakMap([ - [ALARM_FILTER_PANEL_DATA, { - statusList: this.pageLink.statusList, - severityList: this.pageLink.severityList, - typeList: this.pageLink.typeList - } as AlarmFilterPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const providers: StaticProvider[] = [ + { + provide: ALARM_FILTER_PANEL_DATA, + useValue: { + statusList: this.pageLink.statusList, + severityList: this.pageLink.severityList, + typeList: this.pageLink.typeList + } as AlarmFilterPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); const componentRef = overlayRef.attach(new ComponentPortal(AlarmFilterPanelComponent, this.viewContainerRef, injector)); componentRef.onDestroy(() => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts index 7edd0506b3..e3b51c61cb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.ts @@ -18,9 +18,11 @@ import { Component, Inject, InjectionToken, + Injector, Input, OnDestroy, OnInit, + StaticProvider, ViewChild, ViewContainerRef, ViewEncapsulation @@ -41,7 +43,7 @@ import { import { KeyValue } from '@angular/common'; import * as _moment from 'moment'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { MatSelect } from '@angular/material/select'; import { Subscription } from 'rxjs'; import { HistoryWindowType, TimewindowType } from '@shared/models/time/time.models'; @@ -142,18 +144,24 @@ export class DateRangeNavigatorWidgetComponent extends PageComponent implements overlayRef.backdropClick().subscribe(() => { overlayRef.dispose(); }); - const injectionTokens = new WeakMap([ - [DATE_RANGE_NAVIGATOR_PANEL_DATA, { - model: cloneDateRangeNavigatorModel(this.advancedModel), - settings: this.settings, - onChange: model => { - this.advancedModel = model; - this.triggerChange(); - } - } as DateRangeNavigatorPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const providers: StaticProvider[] = [ + { + provide: DATE_RANGE_NAVIGATOR_PANEL_DATA, + useValue: { + model: cloneDateRangeNavigatorModel(this.advancedModel), + settings: this.settings, + onChange: model => { + this.advancedModel = model; + this.triggerChange(); + } + } as DateRangeNavigatorPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); overlayRef.attach(new ComponentPortal(DateRangeNavigatorPanelComponent, this.viewContainerRef, injector)); this.ctx.detectChanges(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index eea93478d1..cc6a6a198b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -99,6 +99,7 @@ [length]="entityDatasource.total() | async" [pageIndex]="pageLink.page" [pageSize]="pageLink.pageSize" - [pageSizeOptions]="pageSizeOptions"> + [pageSizeOptions]="pageSizeOptions" + showFirstLastButtons>
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index f75f07ae69..25b036a467 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -18,9 +18,11 @@ import { AfterViewInit, Component, ElementRef, + Injector, Input, NgZone, OnInit, + StaticProvider, ViewChild, ViewContainerRef } from '@angular/core'; @@ -70,7 +72,7 @@ import { widthStyle } from '@home/components/widget/lib/table-widget.models'; import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { DISPLAY_COLUMNS_PANEL_DATA, DisplayColumnsPanelComponent, @@ -422,17 +424,23 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni }; }); - const injectionTokens = new WeakMap([ - [DISPLAY_COLUMNS_PANEL_DATA, { - columns, - columnsUpdated: (newColumns) => { - this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); - this.displayedColumns.push('actions'); - } - } as DisplayColumnsPanelData], - [OverlayRef, overlayRef] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const providers: StaticProvider[] = [ + { + provide: DISPLAY_COLUMNS_PANEL_DATA, + useValue: { + columns, + columnsUpdated: (newColumns) => { + this.displayedColumns = newColumns.filter(column => column.display).map(column => column.def); + this.displayedColumns.push('actions'); + } + } as DisplayColumnsPanelData + }, + { + provide: OverlayRef, + useValue: overlayRef + } + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); overlayRef.attach(new ComponentPortal(DisplayColumnsPanelComponent, this.viewContainerRef, injector)); this.ctx.detectChanges(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 390ac26d5c..2d7514014d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -175,7 +175,7 @@ export class TbFlot { autoHighlight: this.tooltipIndividual === true, markings: [] }, - selection : { mode : ctx.isMobile ? null : 'x' }, + selection : { mode : 'x' }, legend : { show: false } @@ -702,7 +702,7 @@ export class TbFlot { } public checkMouseEvents() { - const enabled = !this.ctx.isMobile && !this.ctx.isEdit; + const enabled = !this.ctx.isEdit; if (isUndefined(this.mouseEventsEnabled) || this.mouseEventsEnabled !== enabled) { this.mouseEventsEnabled = enabled; if (this.$element) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 57a0563a4e..9da5e679f4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -626,10 +626,14 @@ export default abstract class LeafletMap { } this.points = new FeatureGroup(); } + let pointColor = this.options.pointColor; for (const pointsList of pointsData) { pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { + if (this.options.useColorPointFunction) { + pointColor = safeExecute(this.options.colorPointFunction, [data, pointsData, data.dsIndex]); + } const point = L.circleMarker(this.convertPosition(data), { - color: this.options.pointColor, + color: pointColor, radius: this.options.pointSize }); if (!this.options.pointTooltipOnRightPanel) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 45830cbd55..cbf5883659 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -201,6 +201,8 @@ export type TripAnimationSettings = { pointAsAnchorFunction: GenericFunction; tooltipFunction: GenericFunction; labelFunction: GenericFunction; + useColorPointFunction: boolean; + colorPointFunction: GenericFunction; }; export type actionsHandler = ($event: Event, datasource: Datasource) => void; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index e1cad7a8f7..3a48e8e20f 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -301,6 +301,7 @@ export class MapWidgetController implements MapWidgetInterface { labelFunction: parseFunction(settings.labelFunction, functionParams), tooltipFunction: parseFunction(settings.tooltipFunction, functionParams), colorFunction: parseFunction(settings.colorFunction, functionParams), + colorPointFunction: parseFunction(settings.colorPointFunction, functionParams), polygonColorFunction: parseFunction(settings.polygonColorFunction, functionParams), polygonTooltipFunction: parseFunction(settings.polygonTooltipFunction, functionParams), markerImageFunction: parseFunction(settings.markerImageFunction, ['data', 'images', 'dsData', 'dsIndex']), diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts index 9e1ce715ff..b00e01404a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts @@ -871,6 +871,15 @@ export const pointSchema = title: 'Point color', type: 'string' }, + useColorPointFunction: { + title: 'Use color point function', + type: 'boolean', + default: false + }, + colorPointFunction: { + title: 'Color point function: f(data, dsData, dsIndex)', + type: 'string' + }, pointSize: { title: 'Point size (px)', type: 'number', @@ -899,6 +908,11 @@ export const pointSchema = key: 'pointColor', type: 'color' }, + 'useColorPointFunction', + { + key: 'colorPointFunction', + type: 'javascript' + }, 'pointSize', 'usePointAsAnchor', { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html new file mode 100644 index 0000000000..ec82ff149e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.html @@ -0,0 +1,21 @@ + + + {{settings.icon}} + {{translatedName}} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss new file mode 100644 index 0000000000..18a81aa216 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.scss @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +:host { + width: 100%; + height: 100%; +} + +:host ::ng-deep { + .tb-nav-button { + width: 100%; + height: 100%; + &:hover { + border-bottom: none; + } + &:focus { + border-bottom: none; + } + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 36px; + max-height: 36px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts new file mode 100644 index 0000000000..5dbd8022ce --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-card-widget.component.ts @@ -0,0 +1,66 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { PageComponent } from '@shared/components/page.component'; +import { Component, Input, NgZone, OnInit } from '@angular/core'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { UtilsService } from '@core/services/utils.service'; + +interface NavigationCardWidgetSettings { + name: string; + icon: string; + path: string; +} + +@Component({ + selector: 'tb-navigation-card-widget', + templateUrl: './navigation-card-widget.component.html', + styleUrls: ['./navigation-card-widget.component.scss'] +}) +export class NavigationCardWidgetComponent extends PageComponent implements OnInit { + + settings: NavigationCardWidgetSettings; + + translatedName: string; + + @Input() + ctx: WidgetContext; + + constructor(protected store: Store, + private utils: UtilsService, + private ngZone: NgZone, + private router: Router) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.navigationCardWidget = this; + this.settings = this.ctx.settings; + this.translatedName = this.utils.customTranslation(this.settings.name, this.settings.name); + } + + + navigate($event: Event, path: string) { + $event.preventDefault(); + this.ngZone.run(() => { + this.router.navigateByUrl(path); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html new file mode 100644 index 0000000000..a9ed8e76bb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.html @@ -0,0 +1,37 @@ + + + + + + {{section.name}} + + + + + + {{place.icon}} + + {{place.name}} + + + + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss new file mode 100644 index 0000000000..6a0e60c20b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import '../../../../../../scss/constants'; + +:host { + width: 100%; + height: 100%; +} + +:host ::ng-deep { + .tb-navigation-cards { + .mat-headline { + font-size: 20px; + @media #{$mat-gt-xmd} { + font-size: 24px; + } + } + mat-card { + padding: 0; + margin: 8px; + mat-card-title { + margin: 0; + padding: 24px 16px 16px; + } + mat-card-title+mat-card-content { + padding-top: 0; + } + mat-card-content { + padding: 16px; + } + } + .tb-card-button { + width: 100%; + height: 100%; + max-width: 240px; + &:hover { + border-bottom: none; + } + &:focus { + border-bottom: none; + } + .mat-button-wrapper { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + mat-icon { + margin: auto; + } + span { + height: 18px; + min-height: 36px; + max-height: 36px; + padding: 0 0 20px 0; + margin: auto; + font-size: 18px; + font-weight: 400; + line-height: 18px; + white-space: normal; + } + } + &.mat-raised-button.mat-primary { + .mat-ripple-element { + opacity: 0.3; + background-color: rgba(255, 255, 255, 0.3); + } + } + } + } +} + diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts new file mode 100644 index 0000000000..7628c30457 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/lib/navigation-cards-widget.component.ts @@ -0,0 +1,114 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { PageComponent } from '@shared/components/page.component'; +import { Component, Input, NgZone, OnInit } from '@angular/core'; +import { WidgetContext } from '@home/models/widget-component.models'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MenuService } from '@core/services/menu.service'; +import { HomeSection, HomeSectionPlace } from '@core/services/menu.models'; +import { Router } from '@angular/router'; +import { map } from 'rxjs/operators'; + +interface NavigationCardsWidgetSettings { + filterType: 'all' | 'include' | 'exclude'; + filter: string[]; +} + +@Component({ + selector: 'tb-navigation-cards-widget', + templateUrl: './navigation-cards-widget.component.html', + styleUrls: ['./navigation-cards-widget.component.scss'] +}) +export class NavigationCardsWidgetComponent extends PageComponent implements OnInit { + + homeSections$ = this.menuService.homeSections(); + showHomeSections$ = this.homeSections$.pipe( + map((sections) => { + return sections.filter((section) => this.sectionPlaces(section).length > 0); + }) + ); + + cols = null; + + settings: NavigationCardsWidgetSettings; + + @Input() + ctx: WidgetContext; + + constructor(protected store: Store, + private menuService: MenuService, + private ngZone: NgZone, + private router: Router) { + super(store); + } + + ngOnInit(): void { + this.ctx.$scope.navigationCardsWidget = this; + this.settings = this.ctx.settings; + } + + resize() { + this.updateColumnCount(); + } + + private updateColumnCount() { + this.cols = 2; + const width = this.ctx.width; + if (width >= 1280) { + this.cols = 3; + if (width >= 1920) { + this.cols = 4; + } + } + this.ctx.detectChanges(); + } + + navigate($event: Event, path: string) { + $event.preventDefault(); + this.ngZone.run(() => { + this.router.navigateByUrl(path); + }); + } + + sectionPlaces(section: HomeSection): HomeSectionPlace[] { + return section && section.places ? section.places.filter((place) => this.filterPlace(place)) : []; + } + + private filterPlace(place: HomeSectionPlace): boolean { + if (this.settings.filterType === 'include') { + return this.settings.filter.includes(place.path); + } else if (this.settings.filterType === 'exclude') { + return !this.settings.filter.includes(place.path); + } + return true; + } + + sectionColspan(section: HomeSection): number { + if (this.ctx.width >= 960) { + let colspan = this.cols; + const places = this.sectionPlaces(section); + if (places.length <= colspan) { + colspan = places.length; + } + return colspan; + } else { + return 2; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index b6ff0b2fbb..5f8c94727d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -104,7 +104,8 @@ [length]="source.timeseriesDatasource.total() | async" [pageIndex]="source.pageLink.page" [pageSize]="source.pageLink.pageSize" - [pageSizeOptions]="pageSizeOptions"> + [pageSizeOptions]="pageSizeOptions" + showFirstLastButtons> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index e21e29ba65..4ce340d8ea 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -210,7 +210,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false; this.showTimestamp = this.settings.showTimestamp !== false; - this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.sss'; + this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS'; const pageSize = this.settings.defaultPageSize; if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index 9372e44655..2581da88ad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -112,6 +112,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']); this.settings.tooltipFunction = parseFunction(this.settings.tooltipFunction, ['data', 'dsData', 'dsIndex']); this.settings.labelFunction = parseFunction(this.settings.labelFunction, ['data', 'dsData', 'dsIndex']); + this.settings.colorPointFunction = parseFunction(this.settings.colorPointFunction, ['data', 'dsData', 'dsIndex']); this.normalizationStep = this.settings.normalizationStep; const subscription = this.ctx.defaultSubscription; subscription.callbacks.onDataUpdated = () => { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index eff8557d91..2ad0fb1612 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -35,6 +35,8 @@ import { TripAnimationComponent } from './trip-animation/trip-animation.componen import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; import { ImportExportService } from '@home/components/import-export/import-export.service'; +import { NavigationCardsWidgetComponent } from '@home/components/widget/lib/navigation-cards-widget.component'; +import { NavigationCardWidgetComponent } from '@home/components/widget/lib/navigation-card-widget.component'; import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges-overview-widget.component'; @NgModule({ @@ -52,7 +54,9 @@ import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges- MultipleInputWidgetComponent, TripAnimationComponent, PhotoCameraInputWidgetComponent, - GatewayFormComponent + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent ], imports: [ CommonModule, @@ -71,7 +75,9 @@ import { EdgesOverviewWidgetComponent } from '@home/components/widget/lib/edges- MultipleInputWidgetComponent, TripAnimationComponent, PhotoCameraInputWidgetComponent, - GatewayFormComponent + GatewayFormComponent, + NavigationCardsWidgetComponent, + NavigationCardWidgetComponent ], providers: [ CustomDialogService, diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index 42f67b11bb..e1e3675e89 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -30,6 +30,7 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; import { ActivatedRoute } from '@angular/router'; import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; +import { DAY, historyInterval } from '@shared/models/time/time.models'; export type EntityBooleanFunction> = (entity: T) => boolean; export type EntityStringFunction> = (entity: T) => string; @@ -135,6 +136,7 @@ export class EntityTableConfig, P extends PageLink = P onLoadAction: (route: ActivatedRoute) => void = null; table: EntitiesTableComponent = null; useTimePageLink = false; + defaultTimewindowInterval = historyInterval(DAY); entityType: EntityType = null; tableTitle = ''; selectionEnabled = true; @@ -162,7 +164,7 @@ export class EntityTableConfig, P extends PageLink = P dataSource: (dataLoadedFunction: (col?: number, row?: number) => void) => EntitiesDataSource = (dataLoadedFunction: (col?: number, row?: number) => void) => { return new EntitiesDataSource(this.entitiesFetchFunction, this.entitySelectionEnabled, dataLoadedFunction); - }; + } detailsReadonly: EntityBooleanFunction = () => false; entitySelectionEnabled: EntityBooleanFunction = () => true; deleteEnabled: EntityBooleanFunction = () => true; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index 1df4aed59e..1928a1f404 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -32,6 +32,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { OAuth2Service } from '@core/http/oauth2.service'; import { UserProfileResolver } from '@home/pages/profile/profile-routing.module'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -48,7 +49,7 @@ const routes: Routes = [ { path: 'settings', data: { - auth: [Authority.SYS_ADMIN], + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], breadcrumb: { label: 'admin.system-settings', icon: 'settings' @@ -57,8 +58,13 @@ const routes: Routes = [ children: [ { path: '', - redirectTo: 'general', - pathMatch: 'full' + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + redirectTo: { + SYS_ADMIN: '/settings/general', + TENANT_ADMIN: '/settings/home' + } + } }, { path: 'general', @@ -127,6 +133,19 @@ const routes: Routes = [ resolve: { loginProcessingUrl: OAuth2LoginProcessingUrlResolver } + }, + { + path: 'home', + component: HomeSettingsComponent, + canDeactivate: [ConfirmOnExitGuard], + data: { + auth: [Authority.TENANT_ADMIN], + title: 'admin.home-settings', + breadcrumb: { + label: 'admin.home-settings', + icon: 'settings_applications' + } + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index a170ce10b4..1671220cfe 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -26,6 +26,7 @@ import { HomeComponentsModule } from '@modules/home/components/home-components.m import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; +import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; @NgModule({ declarations: @@ -35,7 +36,8 @@ import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dial SmsProviderComponent, SendTestSmsDialogComponent, SecuritySettingsComponent, - OAuth2SettingsComponent + OAuth2SettingsComponent, + HomeSettingsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html new file mode 100644 index 0000000000..0191724a24 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html @@ -0,0 +1,54 @@ + +
+ + +
+ admin.home-settings +
+
+ + +
+ +
+
+
+
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
+
+
+ +
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss new file mode 100644 index 0000000000..482889cf77 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.scss @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@import "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts new file mode 100644 index 0000000000..a92579d701 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.ts @@ -0,0 +1,84 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, OnInit } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; +import { Router } from '@angular/router'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { DashboardService } from '@core/http/dashboard.service'; +import { HomeDashboardInfo } from '@shared/models/dashboard.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { DashboardId } from '@shared/models/id/dashboard-id'; + +@Component({ + selector: 'tb-home-settings', + templateUrl: './home-settings.component.html', + styleUrls: ['./home-settings.component.scss', './settings-card.scss'] +}) +export class HomeSettingsComponent extends PageComponent implements OnInit, HasConfirmForm { + + homeSettings: FormGroup; + + constructor(protected store: Store, + private router: Router, + private dashboardService: DashboardService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.homeSettings = this.fb.group({ + dashboardId: [null], + hideDashboardToolbar: [true] + }); + this.dashboardService.getTenantHomeDashboardInfo().subscribe( + (homeDashboardInfo) => { + this.setHomeDashboardInfo(homeDashboardInfo); + } + ); + } + + save(): void { + const strDashboardId = this.homeSettings.get('dashboardId').value; + const dashboardId: DashboardId = strDashboardId ? new DashboardId(strDashboardId) : null; + const hideDashboardToolbar = this.homeSettings.get('hideDashboardToolbar').value; + const homeDashboardInfo: HomeDashboardInfo = { + dashboardId, + hideDashboardToolbar + }; + this.dashboardService.setTenantHomeDashboardInfo(homeDashboardInfo).subscribe( + () => { + this.setHomeDashboardInfo(homeDashboardInfo); + } + ); + } + + confirmForm(): FormGroup { + return this.homeSettings; + } + + private setHomeDashboardInfo(homeDashboardInfo: HomeDashboardInfo) { + this.homeSettings.reset({ + dashboardId: homeDashboardInfo?.dashboardId?.id, + hideDashboardToolbar: isDefinedAndNotNull(homeDashboardInfo?.hideDashboardToolbar) ? + homeDashboardInfo?.hideDashboardToolbar : true + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.html b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html index 9c37b567b2..5077950a59 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.html +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.html @@ -79,6 +79,21 @@ customer.description +
+
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss b/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss new file mode 100644 index 0000000000..3a4ec550cd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts index 0323dd1788..20e49d118b 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts @@ -23,12 +23,14 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefinedAndNotNull } from '@core/utils'; import { getCurrentAuthState } from '@core/auth/auth.selectors'; import { AuthState } from '@core/auth/auth.models'; @Component({ selector: 'tb-customer', - templateUrl: './customer.component.html' + templateUrl: './customer.component.html', + styleUrls: ['./customer.component.scss'] }) export class CustomerComponent extends ContactBasedComponent { @@ -58,7 +60,10 @@ export class CustomerComponent extends ContactBasedComponent { title: [entity ? entity.title : '', [Validators.required]], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -69,6 +74,11 @@ export class CustomerComponent extends ContactBasedComponent { this.isPublic = entity.additionalInfo && entity.additionalInfo.isPublic; this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } onCustomerIdCopied(event) { diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html index 35fad79d8d..bd02c9dee3 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html @@ -52,16 +52,16 @@ {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: 0 - }) | async }} + } }} {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: lenMaxClientPublicKeyOrId - }) | async }} + } }} @@ -83,16 +83,16 @@ {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: 0 - }) | async }} + } }} {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: lenMaxClientSecretKey - }) | async }} + } }} diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts index 8d99d3c8d3..a068bb589e 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts @@ -31,7 +31,6 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { PageComponent } from '@shared/components/page.component'; -import { TranslateService } from '@ngx-translate/core'; @Component({ selector: 'tb-security-config-server-lwm2m', @@ -61,7 +60,6 @@ export class SecurityConfigServerComponent extends PageComponent implements OnIn constructor(protected store: Store, @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, public dialogRef: MatDialogRef, - public translate: TranslateService, public fb: FormBuilder) { super(store); } diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html index e130e40a2c..db0946b371 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html @@ -82,16 +82,16 @@ {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: 0 - }) | async }} + } }} {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ translate.get('device.lwm2m-security-config.pattern_hex_dec', { + {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { count: lenMaxKeyClient - }) | async }} + } }} diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts index c1aa7764f0..003ba5d34a 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links-routing.module.ts @@ -14,11 +14,25 @@ /// limitations under the License. /// -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; import { HomeLinksComponent } from './home-links.component'; import { Authority } from '@shared/models/authority.enum'; +import { Observable } from 'rxjs'; +import { HomeDashboard } from '@shared/models/dashboard.models'; +import { DashboardService } from '@core/http/dashboard.service'; + +@Injectable() +export class HomeDashboardResolver implements Resolve { + + constructor(private dashboardService: DashboardService) { + } + + resolve(): Observable { + return this.dashboardService.getHomeDashboard(); + } +} const routes: Routes = [ { @@ -31,12 +45,18 @@ const routes: Routes = [ label: 'home.home', icon: 'home' } + }, + resolve: { + homeDashboard: HomeDashboardResolver } } ]; @NgModule({ imports: [RouterModule.forChild(routes)], - exports: [RouterModule] + exports: [RouterModule], + providers: [ + HomeDashboardResolver + ] }) export class HomeLinksRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html index 85b5b388a7..f519d22637 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.html @@ -15,23 +15,26 @@ limitations under the License. --> - - - - - {{section.name}} - - - - - - {{place.icon}} - - {{place.name}} - - - - - - - + + + + + + + {{section.name}} + + + + + + {{place.icon}} + + {{place.name}} + + + + + + + + diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss index a97e7083e0..20949642ab 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.scss @@ -15,6 +15,11 @@ */ @import '../../../../../scss/constants'; +:host { + width: 100%; + height: 100%; +} + :host ::ng-deep { .tb-home-links { .mat-headline { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts index 7d5348c04f..7f485df45c 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.component.ts @@ -19,6 +19,8 @@ import { MenuService } from '@core/services/menu.service'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { HomeSection } from '@core/services/menu.models'; +import { ActivatedRoute } from '@angular/router'; +import { HomeDashboard } from '@shared/models/dashboard.models'; @Component({ selector: 'tb-home-links', @@ -31,15 +33,20 @@ export class HomeLinksComponent implements OnInit { cols = 2; + homeDashboard: HomeDashboard = this.route.snapshot.data.homeDashboard; + constructor(private menuService: MenuService, - public breakpointObserver: BreakpointObserver) { + public breakpointObserver: BreakpointObserver, + private route: ActivatedRoute) { } ngOnInit() { - this.updateColumnCount(); - this.breakpointObserver - .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) - .subscribe((state: BreakpointState) => this.updateColumnCount()); + if (!this.homeDashboard) { + this.updateColumnCount(); + this.breakpointObserver + .observe([MediaBreakpoints.lg, MediaBreakpoints['gt-lg']]) + .subscribe((state: BreakpointState) => this.updateColumnCount()); + } } private updateColumnCount() { diff --git a/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts index e64b9b1768..644645c1a9 100644 --- a/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-links/home-links.module.ts @@ -20,6 +20,7 @@ import { CommonModule } from '@angular/common'; import { HomeLinksRoutingModule } from './home-links-routing.module'; import { HomeLinksComponent } from './home-links.component'; import { SharedModule } from '@app/shared/shared.module'; +import { HomeComponentsModule } from '@home/components/home-components.module'; @NgModule({ declarations: @@ -29,6 +30,7 @@ import { SharedModule } from '@app/shared/shared.module'; imports: [ CommonModule, SharedModule, + HomeComponentsModule, HomeLinksRoutingModule ] }) diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 7ab52a373a..eaca7133a0 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -63,6 +63,20 @@ +
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss new file mode 100644 index 0000000000..3a4ec550cd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import "../../../../../scss/constants"; + +:host { + .tb-default-dashboard { + tb-dashboard-autocomplete { + @media #{$mat-gt-sm} { + padding-right: 12px; + } + + @media #{$mat-lt-md} { + padding-bottom: 12px; + } + } + mat-checkbox { + @media #{$mat-gt-sm} { + margin-top: 16px; + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index a24a17e6bf..30c0dc36aa 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -23,11 +23,12 @@ import { ActionNotificationShow } from '@app/core/notification/notification.acti import { TranslateService } from '@ngx-translate/core'; import { ContactBasedComponent } from '../../components/entity/contact-based.component'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-tenant', templateUrl: './tenant.component.html', - styleUrls: [] + styleUrls: ['./tenant.component.scss'] }) export class TenantComponent extends ContactBasedComponent { @@ -54,7 +55,10 @@ export class TenantComponent extends ContactBasedComponent { tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], additionalInfo: this.fb.group( { - description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -65,6 +69,11 @@ export class TenantComponent extends ContactBasedComponent { this.entityForm.patchValue({title: entity.title}); this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId}); this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } updateFormState() { diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.html b/ui-ngx/src/app/modules/home/pages/user/user.component.html index fbc5f3c1c7..20821031b0 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.html +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.html @@ -95,6 +95,20 @@ {{ 'user.always-fullscreen' | translate }} +
+ + + {{ 'dashboard.home-dashboard-hide-toolbar' | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index 585723b659..b9326a696f 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -23,7 +23,7 @@ import { User } from '@shared/models/user.model'; import { selectAuth } from '@core/auth/auth.selectors'; import { map } from 'rxjs/operators'; import { Authority } from '@shared/models/authority.enum'; -import { isUndefined } from '@core/utils'; +import { isDefinedAndNotNull, isUndefined } from '@core/utils'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @Component({ @@ -74,6 +74,9 @@ export class UserComponent extends EntityComponent { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], defaultDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null], defaultDashboardFullscreen: [entity && entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false], + homeDashboardId: [entity && entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null], + homeDashboardHideToolbar: [entity && entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true] } ) } @@ -89,6 +92,11 @@ export class UserComponent extends EntityComponent { {defaultDashboardId: entity.additionalInfo ? entity.additionalInfo.defaultDashboardId : null}}); this.entityForm.patchValue({additionalInfo: {defaultDashboardFullscreen: entity.additionalInfo ? entity.additionalInfo.defaultDashboardFullscreen : false}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardId: entity.additionalInfo ? entity.additionalInfo.homeDashboardId : null}}); + this.entityForm.patchValue({additionalInfo: + {homeDashboardHideToolbar: entity.additionalInfo && + isDefinedAndNotNull(entity.additionalInfo.homeDashboardHideToolbar) ? entity.additionalInfo.homeDashboardHideToolbar : true}}); } } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html index 802bb8e65a..e48eba1ebf 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html @@ -35,6 +35,7 @@ [isEditActionEnabled]="true" [isExportActionEnabled]="true" [isRemoveActionEnabled]="!isReadOnly" + [disableWidgetInteraction]="true" [callbacks]="dashboardCallbacks"> diff --git a/ui-ngx/src/app/shared/components/dashboard-select.component.ts b/ui-ngx/src/app/shared/components/dashboard-select.component.ts index 7357a1aa5a..64a8eb42c1 100644 --- a/ui-ngx/src/app/shared/components/dashboard-select.component.ts +++ b/ui-ngx/src/app/shared/components/dashboard-select.component.ts @@ -14,7 +14,17 @@ /// limitations under the License. /// -import { Component, forwardRef, Inject, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { + Component, + forwardRef, + Inject, + Injector, + Input, + OnInit, + StaticProvider, + ViewChild, + ViewContainerRef +} from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { PageLink } from '@shared/models/page/page-link'; @@ -32,7 +42,7 @@ import { CdkOverlayOrigin, ConnectedPosition, Overlay, OverlayConfig, OverlayRef import { BreakpointObserver } from '@angular/cdk/layout'; import { DOCUMENT } from '@angular/common'; import { WINDOW } from '@core/services/window.service'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { DASHBOARD_SELECT_PANEL_DATA, DashboardSelectPanelComponent, @@ -186,12 +196,12 @@ export class DashboardSelectComponent implements ControlValueAccessor, OnInit { overlayRef.attach(new ComponentPortal(DashboardSelectPanelComponent, this.viewContainerRef, injector)); } - private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): PortalInjector { - const injectionTokens = new WeakMap([ - [DASHBOARD_SELECT_PANEL_DATA, data], - [OverlayRef, overlayRef] - ]); - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + private _createDashboardSelectPanelInjector(overlayRef: OverlayRef, data: DashboardSelectPanelData): Injector { + const providers: StaticProvider[] = [ + {provide: DASHBOARD_SELECT_PANEL_DATA, useValue: data}, + {provide: OverlayRef, useValue: overlayRef} + ]; + return Injector.create({parent: this.viewContainerRef.injector, providers}); } private updateView() { diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx index 876d93c6ff..c22779897e 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-radios.tsx @@ -28,12 +28,17 @@ class ThingsboardRadios extends React.Component {this.props.form.title} - { + { this.props.onChangeValidate(e); }}> {items} diff --git a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx index 571f36eab7..dcfe453b61 100644 --- a/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx +++ b/ui-ngx/src/app/shared/components/json-form/react/json-form-rc-select.tsx @@ -22,6 +22,7 @@ import { KeyLabelItem } from '@shared/components/json-form/react/json-form.models'; import { Mode } from 'rc-select/lib/interface'; +import { deepClone } from '@core/utils'; interface ThingsboardRcSelectState extends JsonFormFieldState { currentValue: KeyLabelItem | KeyLabelItem[]; @@ -151,10 +152,14 @@ class ThingsboardRcSelect extends React.Component {options} diff --git a/ui-ngx/src/app/shared/components/time/timewindow.component.ts b/ui-ngx/src/app/shared/components/time/timewindow.component.ts index 6e61680b09..e1be42eff3 100644 --- a/ui-ngx/src/app/shared/components/time/timewindow.component.ts +++ b/ui-ngx/src/app/shared/components/time/timewindow.component.ts @@ -14,7 +14,18 @@ /// limitations under the License. /// -import { Component, forwardRef, Inject, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import { + Component, + forwardRef, + Inject, + Injector, + Input, + OnDestroy, + OnInit, + StaticProvider, + ViewChild, + ViewContainerRef +} from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; import { MillisecondsToTimeStringPipe } from '@shared/pipe/milliseconds-to-time-string.pipe'; @@ -32,7 +43,7 @@ import { TimewindowPanelComponent, TimewindowPanelData } from '@shared/components/time/timewindow-panel.component'; -import { ComponentPortal, PortalInjector } from '@angular/cdk/portal'; +import { ComponentPortal } from '@angular/cdk/portal'; import { MediaBreakpoints } from '@shared/models/constants'; import { BreakpointObserver } from '@angular/cdk/layout'; import { WINDOW } from '@core/services/window.service'; @@ -229,12 +240,12 @@ export class TimewindowComponent implements OnInit, OnDestroy, ControlValueAcces }); } - private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): PortalInjector { - const injectionTokens = new WeakMap([ - [TIMEWINDOW_PANEL_DATA, data], - [OverlayRef, overlayRef] - ]); - return new PortalInjector(this.viewContainerRef.injector, injectionTokens); + private _createTimewindowPanelInjector(overlayRef: OverlayRef, data: TimewindowPanelData): Injector { + const providers: StaticProvider[] = [ + {provide: TIMEWINDOW_PANEL_DATA, useValue: data}, + {provide: OverlayRef, useValue: overlayRef} + ]; + return Injector.create({parent: this.viewContainerRef.injector, providers}); } registerOnChange(fn: any): void { diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index af4494338d..addecfb57a 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -20,9 +20,11 @@ import { Directive, ElementRef, HostBinding, Inject, + Injector, Input, NgZone, OnDestroy, Optional, + StaticProvider, ViewChild, ViewContainerRef } from '@angular/core'; @@ -34,7 +36,6 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { MatButton } from '@angular/material/button'; import Timeout = NodeJS.Timeout; -import { PortalInjector } from '@angular/cdk/portal'; @Directive({ selector: '[tb-toast]' @@ -138,10 +139,10 @@ export class ToastDirective implements AfterViewInit, OnDestroy { this.toastComponentRef.destroy(); } }; - const injectionTokens = new WeakMap([ - [MAT_SNACK_BAR_DATA, data] - ]); - const injector = new PortalInjector(this.viewContainerRef.injector, injectionTokens); + const providers: StaticProvider[] = [ + {provide: MAT_SNACK_BAR_DATA, useValue: data} + ]; + const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); this.toastComponentRef = this.viewContainerRef.createComponent(componentFactory, 0, injector); this.cd.detectChanges(); diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 1637890ce5..a92bc3d672 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -106,6 +106,15 @@ export interface Dashboard extends DashboardInfo { configuration?: DashboardConfiguration; } +export interface HomeDashboard extends Dashboard { + hideDashboardToolbar: boolean; +} + +export interface HomeDashboardInfo { + dashboardId: DashboardId; + hideDashboardToolbar: boolean; +} + export function isPublicDashboard(dashboard: DashboardInfo): boolean { if (dashboard && dashboard.assignedCustomers) { return dashboard.assignedCustomers diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index 86d2ddd89b..dcc89852b7 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -285,6 +285,7 @@ export const dynamicValueSourceTypeTranslationMap = new Map { sourceType: DynamicValueSourceType; sourceAttribute: string; + inherit?: boolean; } export interface FilterPredicateValue { diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 8483fb1113..28fb3a4b48 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -54,7 +54,10 @@ "share-via": "Sdílet přes {{provider}}", "continue": "Pokračovat", "discard-changes": "Zahodit změny", - "download": "Stáhnout" + "download": "Stáhnout", + "next-with-label": "Další: {{label}}", + "read-more": "Zobrazit více", + "hide": "Skrýt" }, "aggregation": { "aggregation": "Agregace", @@ -77,6 +80,8 @@ "test-mail-sent": "Testovací zpráva byla úspěšně odeslána!", "base-url": "Základní URL", "base-url-required": "Hodnota Základní URL je povinná.", + "prohibit-different-url": "Zakázat použití názvu hosta z hlaviček požadavku klienta", + "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", "mail-from": "Email od", "mail-from-required": "Hodnota Email od je povinná.", "smtp-protocol": "SMTP protokol", @@ -99,6 +104,33 @@ "proxy-user": "Uživatel proxy", "proxy-password": "Heslo proxy", "send-test-mail": "Odeslat testovací zprávu", + "sms-provider": "Poskytovatel SMS", + "sms-provider-settings": "Nastavení poskytovatele SMS", + "sms-provider-type": "Typ poskytovatele SMS", + "sms-provider-type-required": "Typ poskytovatele SMS je povinný.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "aws-access-key-id": "AWS Access Key ID", + "aws-access-key-id-required": "AWS Access Key ID je povinný", + "aws-secret-access-key": "AWS Secret Access Key", + "aws-secret-access-key-required": "AWS Secret Access Key je povinný", + "aws-region": "AWS Region", + "aws-region-required": "AWS Region je povinný", + "number-from": "Telefonní číslo odesílatele", + "number-from-required": "Telefonní číslo Odesílatele je povinné.", + "number-to": "Telefonní číslo příjemce", + "number-to-required": "Telefonní číslo příjemce je povinné.", + "phone-number-hint": "Telefonní číslo ve formátu E.164, např. +19995550123", + "phone-number-pattern": "Neplatné telefonní číslo. Mělo by odpovídat formátu E.164, např. +19995550123.", + "sms-message": "SMS zpráva", + "sms-message-required": "SMS zpráva je povinná.", + "sms-message-max-length": "SMS zpráva nemůže být delší než 1600 znaků", + "twilio-account-sid": "Twilio Account SID", + "twilio-account-sid-required": "Twilio Account SID je povinné", + "twilio-account-token": "Twilio Account Token", + "twilio-account-token-required": "Twilio Account Token je povinný", + "send-test-sms": "Odeslat testovací SMS", + "test-sms-sent": "Testovací SMS úspěšně odeslána!", "security-settings": "Bezpečnostní nastavení", "password-policy": "Politika hesel", "minimum-password-length": "Minimální délka hesla", @@ -119,8 +151,74 @@ "general-policy": "Obecná politika", "max-failed-login-attempts": "Maximální počet neúspěšných pokusů o přihlášení před zablokováním účtu", "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ý", - "user-lockout-notification-email": "V případě zablokování uživatelského účtu odeslat upozornění na email" - }, + "user-lockout-notification-email": "V případě zablokování uživatelského účtu odeslat upozornění na email", + "domain-name": "Doménové jméno", + "domain-name-unique": "Doménové jméno a protokol musí být unikátní.", + "error-verification-url": "Doménové jméno by nemělo obsahovat symbol '/' ani ':'. Příklad: thingsboard.io", + "oauth2": { + "access-token-uri": "URI přístupového tokenu", + "access-token-uri-required": "URI přístupového tokenu je povinné.", + "activate-user": "Aktivovat uživatele", + "add-domain": "Přidat doménu", + "delete-domain": "Smazat doménu", + "add-provider": "Přidat poskytovatele", + "delete-provider": "Smazat poskytovatele", + "allow-user-creation": "Povolit vytvoření uživatele", + "always-fullscreen": "Vždy v režimu celé obrazovky", + "authorization-uri": "Autorizační URI", + "authorization-uri-required": "Autorizační URI je povinné.", + "client-authentication-method": "Metoda autentizace klienta", + "client-id": "ID klienta", + "client-id-required": "ID klienta je povinné.", + "client-secret": "Heslo klienta", + "client-secret-required": "Heslo klienta je povinné.", + "custom-setting": "Vlastní nastavení", + "customer-name-pattern": "Vzor názvu zákazníka", + "default-dashboard-name": "Název defaultního dashboardu", + "delete-domain-text": "Budťe opatrní, protože po potvrzení nebudou doména ani žádná data poskytovatele dostupné.", + "delete-domain-title": "Jste si jisti, že chcete smazat nastavení domény '{{domainName}}'?", + "delete-registration-text": "Buďte opatrní, protože po potvrzení nebudou data poskytovatele dostupná.", + "delete-registration-title": "Jste si jisti, že chcete smazat poskytovatele '{{name}}'?", + "email-attribute-key": "Atribut klíče email", + "email-attribute-key-required": "Atribut klíče email je povinný.", + "first-name-attribute-key": "Atribut klíče jméno", + "general": "Obecné", + "jwk-set-uri": "JSON Web Key URI", + "last-name-attribute-key": "Atribut klíče příjmení", + "login-button-icon": "Ikona tlačítka přihlášení", + "login-button-label": "Označení poskytovatele", + "login-button-label-placeholder": "Přihlásit se přes $(Provider label)", + "login-button-label-required": "Označení je povinné.", + "login-provider": "Poskytovatel přihlášení", + "mapper": "Mapper", + "new-domain": "Nová doména", + "oauth2": "OAuth2", + "redirect-uri-template": "Šablona URI přesměrování", + "copy-redirect-uri": "Zkopírovat URI přesměrování", + "registration-id": "ID registrace", + "registration-id-required": "ID registrace je povinné.", + "registration-id-unique": "Id registrace musí být v systému unikátní.", + "scope": "Rozsah", + "scope-required": "Rozsah je povinný.", + "tenant-name-pattern": "Vzor názvu tenanta", + "tenant-name-pattern-required": "Vzor názvu tenanta je povinný.", + "tenant-name-strategy": "Strategie názvu tenanta", + "type": "Typ mapperu", + "uri-pattern-error": "Neplatný formát URI.", + "url": "URL", + "url-pattern": "Neplatný formát URL.", + "url-required": "URL je povinná.", + "user-info-uri": "User info URI", + "user-info-uri-required": "User info URI je povinné.", + "user-name-attribute-name": "Atribut klíče název uživatele", + "user-name-attribute-name-required": "Atribut klíče název uživatele je povinný", + "protocol": "Protokol", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Povolit nastavení OAuth2" + } + }, "alarm": { "alarm": "Alarm", "alarms": "Alarmy", @@ -128,6 +226,8 @@ "no-alarms-matching": "Žádné alarmy odpovídající '{{entity}}' nebyly nalezeny.", "alarm-required": "Alarm je povinný", "alarm-status": "Stav alarmu", + "alarm-status-list": "Seznam stavů alarmu", + "any-status": "Všechny stavy", "search-status": { "ANY": "Všechny", "ACTIVE": "Aktivní", @@ -154,6 +254,8 @@ "end-time": "Datum ukončení", "ack-time": "Datum přijetí", "clear-time": "Datum vyřešení", + "alarm-severity-list": "Seznam závažností alarmu", + "any-severity": "Všechny závažnosti", "severity-critical": "Kritická", "severity-major": "Vysoká", "severity-minor": "Nízká", @@ -176,12 +278,16 @@ "clear-alarm-title": "Odstranit alarm", "clear-alarm-text": "Jste si jisti, že chcete alarm odstranit?", "alarm-status-filter": "Filtr stavu alarmu", + "alarm-filter": "Filtr alarmu", "max-count-load": "Maximální počet nahraných alarmů (0 - neomezeně)", "max-count-load-required": "Maximální počet nahraných alarmů je povinný.", "max-count-load-error-min": "Minimální hodnota je 0.", "fetch-size": "Velikost dávky", "fetch-size-required": "Velikost dávky je povinná.", - "fetch-size-error-min": "Minimální hodnota je 10." + "fetch-size-error-min": "Minimální hodnota je 10.", + "alarm-type-list": "Seznam typů alarmu", + "any-type": "Všechny typy", + "search-propagated-alarms": "Vyhledat zpropagované alarmy" }, "alias": { "add": "Přidat alias", @@ -211,6 +317,7 @@ "filter-type-device-search-query-description": "Zařízení typů {{deviceTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Dotaz na vyhledání zobrazení entity", "filter-type-entity-view-search-query-description": "Entitní pohledy typů {{entityViewTypes}} se {{relationType}} vztahem {{direction}} {{rootEntity}}", + "filter-type-apiUsageState": "Stav využití Api", "entity-filter": "Filtr entity", "resolve-multiple": "Použít jako více entit", "filter-type": "Typ filtru", @@ -325,6 +432,59 @@ "no-attributes-text": "Žádné atributy nebyly nalezeny", "no-telemetry-text": "Žádná telemetrie nebyla nalezena" }, + "api-usage": { + "api-usage": "Využití Api", + "data-points": "Datové body", + "data-points-storage-days": "Dny uložení datových bodů", + "email": "Email", + "email-messages": "Emailové zprávy", + "email-messages-daily-activity": "Denní aktivita emailových zpráv", + "email-messages-hourly-activity": "Hodinová aktivita emailových zpráv", + "email-messages-monthly-activity": "Měsíční aktivita emailových zpráv", + "exceptions": "Výjimky", + "executions": "Zpracování", + "javascript": "JavaScript", + "javascript-executions": "JavaScript výjimky", + "javascript-functions": "JavaScript funkce", + "javascript-functions-daily-activity": "Denní aktivita JavaScript funkcí", + "javascript-functions-hourly-activity": "Hodinová aktivita JavaScript funkcí", + "javascript-functions-monthly-activity": "Měsíční aktivita JavaScript funkcí", + "latest-error": "Poslední chyba", + "messages": "Zprávy", + "permanent-failures": "${entityName} permanentní chyby", + "permanent-timeouts": "${entityName} permanentní timeouty", + "processing-failures": "${entityName} chyby zpracování", + "processing-failures-and-timeouts": "Chyby a timeouty zpracování", + "processing-timeouts": "${entityName} timeouty zpracování", + "queue-stats": "Statistiky fronty", + "rule-chain": "Řetěz pravidel", + "rule-engine": "Engine pro zpracování pravidel", + "rule-engine-daily-activity": "Denní aktivita enginu pro zpracování pravidel", + "rule-engine-executions": "Zpracování Enginu pro zpracování pravidel", + "rule-engine-hourly-activity": "Hodinová aktivita enginu pro zpracování pravidel", + "rule-engine-monthly-activity": "Měsíční aktivita enginu pro zpracování pravidel", + "rule-engine-statistics": "Statistiky enginu pro zpracování pravidel", + "rule-node": "Uzel pravidla", + "sms": "SMS", + "sms-messages": "SMS zprávy", + "sms-messages-daily-activity": "Denní aktivita SMS zpráv", + "sms-messages-hourly-activity": "Hodinová aktivita SMS zpráv", + "sms-messages-monthly-activity": "Měsíční aktivita SMS zpráv", + "successful": "${entityName} úspěšnost", + "telemetry": "Telemetrie", + "telemetry-persistence": "Uložení telemetrie", + "telemetry-persistence-daily-activity": "Denní aktivita uložení telemetrie", + "telemetry-persistence-hourly-activity": "Hodinová aktivita uložení telemetrie", + "telemetry-persistence-monthly-activity": "Měsíční aktivita uložení telemetrie", + "transport": "Přenos", + "transport-daily-activity": "Denní aktivita přenosu", + "transport-data-points": "Datové body přenosu", + "transport-hourly-activity": "Hodinová aktivita přenosu", + "transport-messages": "Zprávy přenosu", + "transport-monthly-activity": "Měsíční aktivita přenosu", + "view-details": "Zobrazit detail", + "view-statistics": "Zobrazit statistiky" + }, "audit-log": { "audit": "Audit", "audit-logs": "Záznamy auditu", @@ -363,7 +523,13 @@ "action-data": "Data akce", "failure-details": "Detail chyby", "search": "Prohledat záznamy auditu", - "clear-search": "Vymazat vyhledávání" + "clear-search": "Vymazat vyhledávání", + "type-assigned-from-tenant": "Odebráno tenantovi", + "type-assigned-to-tenant": "Přiřazeno tenantovi", + "type-provision-success": "Zřízení zařízení", + "type-provision-failure": "Selhání zřízení zařízení", + "type-timeseries-updated": "Aktualizace telemetrie", + "type-timeseries-deleted": "Smazání telemetrie" }, "confirm-on-exit": { "message": "Některé změny nebyly uloženy. Jste si jisti, že chcete tuto stránku opustit?", @@ -549,6 +715,7 @@ "title-color": "Barva názvu", "display-dashboards-selection": "Zobrazit výběr dashboardů", "display-entities-selection": "Zobrazit výběr entit", + "display-filters": "Zobrazit filtry", "display-dashboard-timewindow": "Zobrazit časové okno", "display-dashboard-export": "Zobrazit export", "import": "Importovat dashboard", @@ -615,6 +782,7 @@ "alarm": "Pole alarmu", "timeseries-required": "Časové řady entity jsou povinné.", "timeseries-or-attributes-required": "Časové řady / atributy entity jsou povinné.", + "alarm-fields-timeseries-or-attributes-required": "Pole alarmu nebo časové řady / atributy jsou povinné.", "maximum-timeseries-or-attributes": "Maximálně { count, plural, 1 {1 časová řada/atribut je povolena.} other {# časových řad/atributů je povoleno} }", "alarm-fields-required": "Pole alarmu jsou povinná.", "function-types": "Typy funkcí", @@ -706,6 +874,12 @@ "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 20 znaků.", "rsa-key": "RSA veřejný klíč", "rsa-key-required": "RSA veřejný klíč je povinný.", + "client-id": "ID klienta", + "client-id-pattern": "Obsahuje neplatné znaky.", + "user-name": "Název uživatele", + "user-name-required": "Název uživatele je povinný.", + "client-id-or-user-name-necessary": "ID klienta a/nebo název uživatele jsou povinné", + "password": "Heslo", "secret": "Heslo", "secret-required": "Heslo je povinné.", "device-type": "Typ zařízení", @@ -724,19 +898,183 @@ "details": "Detail", "copyId": "Kopírovat Id zařízení", "copyAccessToken": "Kopírovat přístupový token", + "copy-mqtt-authentication": "Kopírovat přístupové údaje MQTT", "idCopiedMessage": "Id zařízení bylo zkopírováno do schránky", "accessTokenCopiedMessage": "Přístupový token zařízení byl zkopírován do schránky", + "mqtt-authentication-copied-message": "MQTT autentizace zařízení byla zkopírována do schránky", "assignedToCustomer": "Přiřazeno zákazníkovi", "unable-delete-device-alias-title": "Nebylo možné smazat alias zařízení", "unable-delete-device-alias-text": "Alias zařízení '{{deviceAlias}}' nelze smazat, protože je používán následujícími widgety:
{{widgetsList}}", "is-gateway": "Je bránou", + "overwrite-activity-time": "Přepsat čas aktivity připojeného zařízení", "public": "Veřejné", "device-public": "Zařízení je veřejné", "select-device": "Vybrat zařízení", "import": "Importovat zařízení", "device-file": "Soubor zařízení", "search": "Vyhledat zařízení", - "selected-devices": "Vybráno { count, plural, 1 {1 zařízení} other {# zařízení} }" + "selected-devices": "Vybráno { count, plural, 1 {1 zařízení} other {# zařízení} }", + "device-configuration": "Konfigurace zařízení", + "transport-configuration": "Konfigurace přenosu", + "wizard": { + "device-wizard": "Průvodce zařízením", + "device-details": "Detail zařízení", + "new-device-profile": "Vytvořit nový profil zařízení", + "existing-device-profile": "Vybrat existující profil zařízení", + "specific-configuration": "Specifická konfigurace", + "customer-to-assign-device": "Přiřadit zařízení zákazníkovi", + "add-credential": "Přidat přístupový údaj" + } + }, + "device-profile": { + "device-profile": "Profil zařízení", + "device-profiles": "Profily zařízení", + "all-device-profiles": "Všechny", + "add": "Přidat profil zařízení", + "edit": "Editovat profil zařízení", + "device-profile-details": "Detail profilu zařízení", + "no-device-profiles-text": "Žádné profily zařízení nebyly nalezeny", + "search": "Vyhledat profily zařízení", + "selected-device-profiles": "Vybráno { count, plural, 1 {1 profil zařízení} other {# profilů zařízení} }", + "no-device-profiles-matching": "Žádný profil zařízení odpovídající '{{entity}}' nebyl nalezen.", + "device-profile-required": "Profil zařízení je povinný", + "idCopiedMessage": "Id profilu zařízení bylo zkopírováno do schránky", + "set-default": "Učinit profil zařízení defaultním", + "delete": "Smazat profil zařízení", + "copyId": "Kopírovat Id profilu zařízení", + "new-device-profile-name": "Název profilu zařízení", + "new-device-profile-name-required": "Název profilu zařízení je povinný.", + "name": "Název", + "name-required": "Název je povinný.", + "type": "Typ profilu", + "type-required": "Typ profilu je povinný.", + "type-default": "Defaultní", + "transport-type": "Typ přenosu", + "transport-type-required": "Typ přenosu je povinný.", + "transport-type-default": "Defaultní", + "transport-type-default-hint": "Podporuje základní MQTT, HTTP and CoAP přenos", + "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "Umožňuje pokročilé nastavení MQTT přenosu", + "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "Typ transportu LWM2M", + "description": "Popis", + "default": "Defaultní", + "profile-configuration": "Konfigurace profilu", + "transport-configuration": "Konfigurace přenosu", + "default-rule-chain": "Defaultní řetěz pravidel", + "select-queue-hint": "Vyberte z rozbalovacího seznamu nebo přidejte vlastní název.", + "delete-device-profile-title": "Jste si jisti, že chcete smazat profil zařízení '{{deviceProfileName}}'?", + "delete-device-profile-text": "Buďte opatrní, protože po potvrzení nebude možné profil zařízení ani žádná související data obnovit.", + "delete-device-profiles-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 profil zařízení} other {# profilů zařízení} }?", + "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.", + "set-default-device-profile-title": "Jste si jisti, že chcete profil zařízení '{{deviceProfileName}}' učinit defaultním?", + "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.", + "no-device-profiles-found": "Žádné profily zařízení nebyly nalezeny.", + "create-new-device-profile": "Vytvořit nový!", + "mqtt-device-topic-filters": "Filtry MQTT fronty zařízení", + "mqtt-device-topic-filters-unique": "Filtry MQTT fronty zařízení musí být unikátní.", + "mqtt-device-payload-type": "MQTT zpráva zařízení", + "mqtt-device-payload-type-json": "JSON", + "mqtt-device-payload-type-proto": "Protobuf", + "mqtt-payload-type-required": "Typ zprávy je povinný.", + "support-level-wildcards": "Jsou podporovány jednoúrovňové [+] a víceúrovňové [#] zástupné znaky.", + "telemetry-topic-filter": "Filtr fronty telemetrie", + "telemetry-topic-filter-required": "Filtr fronty telemetrie je povinný.", + "attributes-topic-filter": "Filtr atributů fronty", + "attributes-topic-filter-required": "Filtr atributů fronty je povinný.", + "telemetry-proto-schema": "Proto schéma telemetrie", + "telemetry-proto-schema-required": "Proto schéma telemetrie je povinné.", + "attributes-proto-schema": "Atributy proto schémata", + "attributes-proto-schema-required": "Atributy proto schémata jsou povinné.", + "rpc-response-topic-filter": "Filtr fronty RPC odpovědi", + "rpc-response-topic-filter-required": "Filtr fronty RPC odpovědi je povinný.", + "not-valid-pattern-topic-filter": "Neplatný vzor filtru fronty", + "not-valid-single-character": "Neplatné použití jednoúrovňového zástupného znaku", + "not-valid-multi-character": "Neplatné použití víceúrovňového zástupného znaku", + "single-level-wildcards-hint": "[+] je vhodný pro jakoukoli úroveň filtru fronty. Př.: v1/devices/+/telemetry or +/devices/+/attributes.", + "multi-level-wildcards-hint": "[#] může nahradit filtr fronty a může se jednat o poslední symbol fronty. Př.: # or v1/devices/me/#.", + "alarm-rules": "Pravidla alarmu", + "alarm-rules-with-count": "Pravidla alarmu ({{count}})", + "no-alarm-rules": "Žádná pravidla alarmu nejsou konfigurována", + "add-alarm-rule": "Přidat pravidlo alarmu", + "edit-alarm-rule": "Editovat pravidlo alarmu", + "alarm-type": "Typ alarmu", + "alarm-type-required": "Typ alarmu je povinný.", + "alarm-type-unique": "Typ alarmu musí být v rámci pravidel alarmu profilu zařízení unikátní.", + "create-alarm-pattern": "Vytvořit {{alarmType}} alarm", + "create-alarm-rules": "Vytvořit pravidla alarmu", + "no-create-alarm-rules": "Nejsou konfigurovány žádné podmínky vytvoření", + "add-create-alarm-rule-prompt": "Přidejte prosím pravidlo vytvoření alarmu", + "clear-alarm-rule": "Pravidlo zrušení alarmu", + "no-clear-alarm-rule": "Není konfigurována žádná podmínka zrušení", + "add-create-alarm-rule": "Přidat podmínku vytvoření", + "add-clear-alarm-rule": "Přidat podmínku zrušení", + "select-alarm-severity": "Vybrat závažnost alarmu", + "alarm-severity-required": "Závažnost alarmu je povinná.", + "condition-duration": "Doba trvání podmínky", + "condition-duration-value": "Hodnota doby trvání", + "condition-duration-time-unit": "Jednotka času", + "condition-duration-value-range": "Hodnota doby trvání musí být v rozsahu od 1 do 2147483647.", + "condition-duration-value-pattern": "Doba trvání musí být celé číslo.", + "condition-duration-value-required": "Doba trvání je povinná.", + "condition-duration-time-unit-required": "Jednotka času je povinná.", + "advanced-settings": "Pokročilá nastavení", + "alarm-rule-details": "Detail", + "add-alarm-rule-details": "Přidat detail", + "propagate-alarm": "Propagovat alarm", + "alarm-rule-relation-types-list": "Typy vztahů ke zpropagování", + "alarm-rule-relation-types-list-hint": "Pokud nejsou vybrány žádné typy vztahů, alarmy budou propagovány bez filtru typu vztahu.", + "alarm-details": "Detail alarmu", + "alarm-rule-condition": "Podmínka pravidla alarmu", + "enter-alarm-rule-condition-prompt": "Přidejte prosím podmínku pravidla alarmu", + "edit-alarm-rule-condition": "Editovat podmínku pravidla alarmu", + "device-provisioning": "Zřízení zařízení", + "provision-strategy": "Strategie zřízení", + "provision-strategy-required": "Strategie zřízení je povinná.", + "provision-strategy-disabled": "Zakázáno", + "provision-strategy-created-new": "Povolit vytváření nových zařízení", + "provision-strategy-check-pre-provisioned": "Zkontrolovat předvytvořená zařízení", + "provision-device-key": "Klíč pro zřízení zařízení", + "provision-device-key-required": "Klíč pro zřízení zařízení je povinný.", + "copy-provision-key": "Kopírovat klíč pro zřízení", + "provision-key-copied-message": "Klíč pro zřízení byl zkopírován do schránky", + "provision-device-secret": "Heslo pro zřízení zařízení", + "provision-device-secret-required": "Heslo pro zřízení zařízení je povinné.", + "copy-provision-secret": "Kopírovat heslo pro zřízení", + "provision-secret-copied-message": "Heslo pro zřízení zařízení bylo zkopírováno do schránky", + "condition": "Podmínka", + "condition-type": "Typ podmínky", + "condition-type-simple": "Jednoduchá", + "condition-type-duration": "Doba trvání", + "condition-during": "V průběhu {{during}}", + "condition-type-repeating": "Opakování", + "condition-type-required": "Typ podmínky je povinný.", + "condition-repeating-value": "Počet událostí", + "condition-repeating-value-range": "Počet událostí musí být v rozsahu od 1 do 2147483647.", + "condition-repeating-value-pattern": "Počet událostí musí být celé číslo.", + "condition-repeating-value-required": "Počet událostí je povinný.", + "condition-repeat-times": "Opakování { count, plural, 1 {1 krát} other {# krát} }", + "schedule-type": "Typ plánovače", + "schedule-type-required": "Typ plánovače je povinný.", + "schedule": "Časový plán", + "edit-schedule": "Editovat časový plán alarmu", + "schedule-any-time": "Aktivní neustále", + "schedule-specific-time": "Aktivní v konkrétním čase", + "schedule-custom": "Vlastní", + "schedule-day": { + "monday": "Pondělí", + "tuesday": "Úterý", + "wednesday": "Středa", + "thursday": "Čtvrtek", + "friday": "Pátek", + "saturday": "Sobota", + "sunday": "Neděle" + }, + "schedule-days": "Dny", + "schedule-time": "Čas", + "schedule-time-from": "Od", + "schedule-time-to": "Do", + "schedule-days-of-week-required": "Musí být vybrán minimálně jeden den v týdnu." }, "dialog": { "close": "Zavřít dialog" @@ -757,7 +1095,7 @@ "entity-alias": "Alias entity", "unable-delete-entity-alias-title": "Alias entity nebylo možné smazat", "unable-delete-entity-alias-text": "Alias entity '{{entityAlias}}' nelze smazat, protože je používán následujícími widgety:
{{widgetsList}}", - "duplicate-alias-error": "Nalezen dupliticní alias '{{alias}}'.
Aliasy entit musí být v rámci dashboardu unikátní.", + "duplicate-alias-error": "Nalezen duplicitní alias '{{alias}}'.
Aliasy entit musí být v rámci dashboardu unikátní.", "missing-entity-filter-error": "Ve filtru chybí alias '{{alias}}'.", "configure-alias": "Konfigurovat '{{alias}}' alias", "alias": "Alias", @@ -794,6 +1132,10 @@ "type-devices": "Zařízení", "list-of-devices": "{ count, plural, 1 {Jedno zařízení} other {Seznam # zařízení} }", "device-name-starts-with": "Zařízení, jejichž název začíná '{{prefix}}'", + "type-device-profile": "Profil zařízení", + "type-device-profiles": "Profily zařízení", + "list-of-device-profiles": "{ count, plural, 1 {Jeden profil zařízení} other {Seznam # profilů zařízení} }", + "device-profile-name-starts-with": "Profily zařízení, jejichž název začíná '{{prefix}}'", "type-asset": "Aktivum", "type-assets": "Aktiva", "list-of-assets": "{ count, plural, 1 {Jedno aktivum} other {Seznam # aktiv} }", @@ -814,6 +1156,10 @@ "type-tenants": "Tenanti", "list-of-tenants": "{ count, plural, 1 {Jeden tenant} other {Seznam # tenantů} }", "tenant-name-starts-with": "Tenanti, jejichž název začíná '{{prefix}}'", + "type-tenant-profile": "Profil tenanta", + "type-tenant-profiles": "Profily tenantů", + "list-of-tenant-profiles": "{ count, plural, 1 {Jeden profil tenanta} other {Seznam # profilů tenantů} }", + "tenant-profile-name-starts-with": "Profily tenantů, jejichž název začíná '{{prefix}}'", "type-customer": "Zákazník", "type-customers": "Zákazníci", "list-of-customers": "{ count, plural, 1 {Jeden zákazník} other {Seznam # zákazníků} }", @@ -840,6 +1186,8 @@ "rulenode-name-starts-with": "Uzly pravidel, jejichž název začíná '{{prefix}}'", "type-current-customer": "Stávající zákazník", "type-current-tenant": "Stávající tenant", + "type-current-user": "Stávající uživatel", + "type-current-user-owner": "Vlastník stávajícího uživatele", "search": "Vyhledat entity", "selected-entities": "{ count, plural, 1 {1 entita} other {# entit} } zvoleno", "entity-name": "Název entity", @@ -847,7 +1195,8 @@ "details": "Detail entity", "no-entities-prompt": "Žádné entity nebyly nalezeny", "no-data": "Nelze zobrazit žádná data", - "columns-to-display": "Zobrazit sloupce" + "columns-to-display": "Zobrazit sloupce", + "type-api-usage-state": "Stav využití API" }, "entity-field": { "created-time": "Datum vytvoření", @@ -1048,7 +1397,7 @@ "anonymous": "Anonymní", "basic": "Základní", "pem": "PEM", - "ca-cert": "soubor CA certifikátu *", + "ca-cert": "Soubor CA certifikátu *", "private-key": "Soubor privátního klíče *", "cert": "Soubor certifikátu *", "no-file": "Žádný soubor nebyl vybrán.", @@ -1154,6 +1503,93 @@ "file": "Soubor rozšíření", "invalid-file-error": "Neplatný soubor rozšíření" }, + "filter": { + "add": "Přidat filtr", + "edit": "Editovat filtr", + "name": "Název filtru", + "name-required": "Název filtru je povinný.", + "duplicate-filter": "Filtr s identickým názvem již existuje.", + "filters": "Filtry", + "unable-delete-filter-title": "Smazat filtr není možné", + "unable-delete-filter-text": "Filtr '{{filter}}' není možné smazat, protože je používán následujícím widgetem(y):
{{widgetsList}}", + "duplicate-filter-error": "Nalezen duplicitní filtr '{{filter}}'.
Filtry musí být v rámci dashboardu unikátní.", + "missing-key-filters-error": "U filtru '{{filter}}' chybí klíčové filtry.", + "filter": "Filtr", + "editable": "Editovatelné", + "no-filters-found": "Žádné filtry nebyly nalezeny.", + "no-filter-text": "Není specifikován žádný filtr", + "add-filter-prompt": "Přidejte prosím filtr", + "no-filter-matching": "'{{filter}}' nebyl nalezen.", + "create-new-filter": "Vytvořit nový!", + "filter-required": "Filtr je povinný.", + "operation": { + "operation": "Operace", + "equal": "je rovno", + "not-equal": "není rovno", + "starts-with": "začíná na", + "ends-with": "končí na", + "contains": "obsahuje", + "not-contains": "neobsahuje", + "greater": "větší než", + "less": "menší než", + "greater-or-equal": "větší nebo rovno", + "less-or-equal": "menší nebo rovno", + "and": "a", + "or": "nebo" + }, + "ignore-case": "ignorovat velikost písmen", + "value": "Hodnota", + "remove-filter": "Odebrat filtr", + "preview": "Náhled filtru", + "no-filters": "Nejsou konfigurovány žádné filtry", + "add-filter": "Přidat filtr", + "add-complex-filter": "Přidat komplexní filtr", + "add-complex": "Přidat komplex", + "complex-filter": "Komplexní filtr", + "edit-complex-filter": "Editovat komplexní filtr", + "edit-filter-user-params": "Editovat filtr predikátu parametrů uživatele", + "filter-user-params": "Filtr predikátu parametrů uživatele", + "user-parameters": "Parametry uživatele", + "display-label": "Zobrazované označení", + "autogenerated-label": "Automaticky vygenerovat označení", + "order-priority": "Priority pořadí polí", + "key-filter": "Klíčový filtr", + "key-filters": "Klíčové filtry", + "key-name": "Název klíče", + "key-name-required": "Název klíče je povinný.", + "key-type": { + "key-type": "Typ klíče", + "attribute": "Atribut", + "timeseries": "Časové řady", + "entity-field": "Pole entity" + }, + "value-type": { + "value-type": "Typ hodnoty", + "string": "Řetězec", + "numeric": "Číslo", + "boolean": "Pravdivostní hodnota", + "date-time": "Datum a čas" + }, + "value-type-required": "Typ hodnoty klíče je povinný.", + "key-value-type-change-title": "Jste si jisti, že chcete změnit typ klíče hodnoty?", + "key-value-type-change-message": "Pokud potvrdíte nový typ hodnoty, všechny zadané klíčové filtry budou odstraněny.", + "no-key-filters": "Nejsou konfigurovány žádné klíčové filtry", + "add-key-filter": "Přidat klíčový filtr", + "remove-key-filter": "Odebrat klíčový filtr", + "edit-key-filter": "Editovat klíčový filtr", + "date": "Datum", + "time": "Čas", + "current-tenant": "Stávající tenant", + "current-customer": "Stávající zákazník", + "current-user": "Stávající uživatel", + "current-device": "Stávající zařízení", + "default-value": "Defaultní hodnota", + "dynamic-source-type": "Dynamický typ zdroje", + "no-dynamic-value": "Žádná dynamická hodnota", + "source-attribute": "Atribut zdroje", + "switch-to-dynamic-value": "Přepnout na dynamickou hodnotu", + "switch-to-default-value": "Přepnout na defaultní hodnotu" + }, "fullscreen": { "expand": "Rozšířit do režimu celé obrazovky", "exit": "Ukončit režim celé obrazovky", @@ -1286,6 +1722,7 @@ "entity-field": "Pole entity", "access-token": "Přístupový token", "isgateway": "Je bránou", + "activity-time-from-gateway-device": "Čas aktivity ze zařízení brány", "description": "Popis" }, "stepper-text":{ @@ -1329,6 +1766,7 @@ "legend": { "direction": "Směr legendy", "position": "Pozice legendy", + "sort-legend": "Setřídit datové klíče v legendě", "show-max": "Zobrazit max hodnotu", "show-min": "Zobrazit min hodnotu", "show-avg": "Zobrazit průměrnou hodnotu", @@ -1525,6 +1963,12 @@ "help": "Nápověda", "reset-debug-mode": "Resetovat režim ladění na všech uzlech" }, + "timezone": { + "timezone": "Časová zóna", + "select-timezone": "Vyberte časovou zónu", + "no-timezones-matching": "žádné časové zóny odpovídající '{{timezone}}' nebyly nalezeny.", + "timezone-required": "Časová zóna je povinná." + }, "queue": { "select_name": "Vybrat název fronty", "name": "Název fronty", @@ -1563,6 +2007,87 @@ "isolated-tb-core-details": "Vyžaduje samostatnou mikroslužbu(y) pro každého izolovaného tenanta", "isolated-tb-rule-engine-details": "Vyžaduje samostatnou mikroslužbu(y) pro každého izolovaného tenanta" }, + "tenant-profile": { + "tenant-profile": "Profil tenanta", + "tenant-profiles": "Profily tenantů", + "add": "Přidat profil tenanta", + "edit": "Editovat profil tenanta", + "tenant-profile-details": "Detail profilu tenanta", + "no-tenant-profiles-text": "Nebyly nalezeny žádné profily tenantů", + "search": "Vyhledat profily tenantů", + "selected-tenant-profiles": "Vybráno { count, plural, 1 {1 profilů tenantů} other {# profilů tenantů} }", + "no-tenant-profiles-matching": "Žádné profily tenantů odpovídající '{{entity}}' nebyly nalezeny.", + "tenant-profile-required": "Profil tenanta je povinný", + "idCopiedMessage": "Id profilu tenanta bylo zkopírováno do schránky", + "set-default": "Učinit profil tenanta defaultním", + "delete": "Smazat profil tenanta", + "copyId": "Kopírovat Id profilu tenanta", + "name": "Název", + "name-required": "Název je povinný.", + "data": "Data profilu", + "profile-configuration": "Konfigurace profilu", + "description": "Popis", + "default": "Defaultní", + "delete-tenant-profile-title": "Jste si jisti, že chcete smazat profil tenanta '{{tenantProfileName}}'?", + "delete-tenant-profile-text": "Buďte opatrní, protože po potvrzení nebude možné profil tenanta ani žádná související data obnovit.", + "delete-tenant-profiles-title": "Jste si jisti, že chcete smazat { count, plural, 1 {1 profil tenanta} other {# profilů tenanta} }?", + "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.", + "set-default-tenant-profile-title": "Jste si jisti, že chcete učinit profil tenanta '{{tenantProfileName}}' defaultním?", + "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.", + "no-tenant-profiles-found": "Nebyly nalezeny žádné profily tenantů.", + "create-new-tenant-profile": "Vytvořit nový!", + "maximum-devices": "Maximální počet zařízení (0 - neomezeno)", + "maximum-devices-required": "Maximální počet zařízení je povinný.", + "maximum-devices-range": "Minimální počet zařízení nemůže být záporný", + "maximum-assets": "Maximální počet aktiv (0 - neomezeno)", + "maximum-assets-required": "Maximální počet aktiv je povinný.", + "maximum-assets-range": "Maximální počet aktiv nemůže být záporný", + "maximum-customers": "Maximální počet zákazníků (0 - neomezeno)", + "maximum-customers-required": "Maximální počet zákazníkůje povinný.", + "maximum-customers-range": "Maximální počet zákazníků nemůže být záporný", + "maximum-users": "Maximální počet uživatelů (0 - neomezeno)", + "maximum-users-required": "Maximální počet uživatelů je povinný.", + "maximum-users-range": "Maximální počet uživatelů nemůže být záporný", + "maximum-dashboards": "Maximální počet dashboardů (0 - neomezeno)", + "maximum-dashboards-required": "Maximální počet dashboardů je povinný.", + "maximum-dashboards-range": "Maximální počet dashboardů nemůže být záporný", + "maximum-rule-chains": "Maximální počet řetězů pravidel (0 - neomezeno)", + "maximum-rule-chains-required": "Maximální počet řetězů pravidel je povinný.", + "maximum-rule-chains-range": "Maximální počet řetězů pravidel nemůže být záporný", + "transport-tenant-msg-rate-limit": "Limit přenosu zpráv tenanta.", + "transport-tenant-telemetry-msg-rate-limit": "Limit přenosu zpráv telemetrie tenanta.", + "transport-tenant-telemetry-data-points-rate-limit": "Limit přenosu datových bodů telemetrie tenanta.", + "transport-device-msg-rate-limit": "Limit přenosu zpráv zařízení.", + "transport-device-telemetry-msg-rate-limit": "Limit přenosu zpráv zařízení telemetrie tenanta.", + "transport-device-telemetry-data-points-rate-limit": "Limit přenosu datových bodů zařízení telemetrie tenanta.", + "max-transport-messages": "Maximální počet zpráv přenosu (0 - neomezeno)", + "max-transport-messages-required": "Maximální počet zpráv přenosu je povinný.", + "max-transport-messages-range": "Maximální počet zpráv přenosu nemůže být záporný", + "max-transport-data-points": "Maximální počet datových bodů přenosu (0 - neomezeno)", + "max-transport-data-points-required": "Maximální počet datových bodů přenosu je povinný.", + "max-transport-data-points-range": "Maximální počet datových bodů přenosu nemůže být záporný", + "max-r-e-executions": "Maximální počet zpracování enginu pro zpracování pravidel (0 - neomezeno)", + "max-r-e-executions-required": "Maximální počet zpracování enginu pro zpracování pravidel je povinný.", + "max-r-e-executions-range": "Maximální počet zpracování enginu pro zpracování pravidel nemůže být záporný", + "max-j-s-executions": "Maximální počet JavaScript zpracování (0 - neomezeno)", + "max-j-s-executions-required": "Maximální počet JavaScript zpracování je povinný.", + "max-j-s-executions-range": "Maximální počet JavaScript zpracování nemůže být záporný", + "max-d-p-storage-days": "Maximální počet dnů uložení datových bodů (0 - neomezeno)", + "max-d-p-storage-days-required": "Maximální počet dnů uložení datových bodů je povinný.", + "max-d-p-storage-days-range": "Maximální počet dnů uložení datových bodů nemůže být záporný", + "default-storage-ttl-days": "Defaultní počet dnů TTL úložiště (0 - neomezeno)", + "default-storage-ttl-days-required": "Defaultní počet dnů TTL úložiště je povinný.", + "default-storage-ttl-days-range": "Defaultní počet dnů TTL úložiště nemůže být záporný", + "max-rule-node-executions-per-message": "Maximální počet zpracování uzlů pravidel na zprávu (0 - neomezeno)", + "max-rule-node-executions-per-message-required": "Maximální počet zpracování uzlů pravidel na zprávu je povinný.", + "max-rule-node-executions-per-message-range": "Maximální počet zpracování uzlů pravidel na zprávu nemůže být záporný", + "max-emails": "Maximální počet odeslaných emailů (0 - neomezeno)", + "max-emails-required": "Maximální počet odeslaných emailů je povinný.", + "max-emails-range": "Maximální počet odeslaných emailů nemůže být záporný", + "max-sms": "Maximální počet odeslaných SMS (0 - neomezeno)", + "max-sms-required": "Maximální počet odeslaných SMS je povinný.", + "max-sms-range": "Maximální počet odeslaných SMS nemůže být záporný" + }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 vteřina} other {# vteřin} }", "minutes-interval": "{ minutes, plural, 1 {1 minuta} other {# minut} }", @@ -1574,8 +2099,14 @@ "seconds": "Vteřiny", "advanced": "Rozšířené" }, + "timeunit": { + "seconds": "Vteřiny", + "minutes": "Minuty", + "hours": "Hodiny", + "days": "Dny" + }, "timewindow": { - "days": "{ days, plural, 1 { den } other {# days } }", + "days": "{ days, plural, 1 { den } other {# dnů } }", "hours": "{ hours, plural, 0 { hodina } 1 {1 hodina } other {# hodin } }", "minutes": "{ minutes, plural, 0 { minuta } 1 {1 minuta } other {# minut } }", "seconds": "{ seconds, plural, 0 { vteřina } 1 {1 vteřina } other {# vteřin } }", @@ -1694,6 +2225,7 @@ "type": "Typ widgetu", "resources": "Zdroje", "resource-url": "JavaScript/CSS URL", + "resource-is-module": "Je modulem", "remove-resource": "Odebrat zdroj", "add-resource": "Přidat zdroj", "html": "HTML", @@ -1711,7 +2243,10 @@ "widget-template-load-failed-error": "Nahrání šablony widgetu selhalo!", "add": "Přidat widget", "undo": "Vrátit změny widgetu", - "export": "Exportovat widget" + "export": "Exportovat widget", + "no-data": "Nejsou k dispozici žádná data pro zobrazení ve widgetu", + "data-overflow": "Widget zobrazuje {{count}} z {{total}} entit", + "alarm-data-overflow": "Widget zobrazuje alarmy {{allowedEntities}} (maxima možných) entit z {{totalEntities}} entit" }, "widget-action": { "header-button": "Tlačítko hlavičky widgetu", @@ -1724,7 +2259,14 @@ "target-dashboard-state-required": "Cílový stav dashboardu je povinný", "set-entity-from-widget": "Nastavit entitu z widgetu", "target-dashboard": "Cílový dashboard", - "open-right-layout": "Otevřít rozmístění dashboardu vpravo (mobilní zobrazení)" + "open-right-layout": "Otevřít rozmístění dashboardu vpravo (mobilní zobrazení)", + "open-in-separate-dialog": "Otevřít v samostatném okně", + "dialog-title": "Název okna", + "dialog-hide-dashboard-toolbar": "Skrýt v okně nástrojovou lištu dashboardu", + "dialog-width": "Šířka okna v procentech vzhledem k šířce obrazovky", + "dialog-height": "Výška okna v procentech vzhledem k výšce obrazovky", + "dialog-size-range-error": "Hodnota procentuální velikosti musí být v rozsahu od 1 do 100.", + "open-new-browser-tab": "Otevřít na nové záložce prohlížeče" }, "widgets-bundle": { "current": "Vybraná kategorie", @@ -1891,8 +2433,11 @@ "entity-coordinate-required": "Obě pole, zeměpisná šířka i zeměpisná délka, jsou povinná", "entity-timeseries-required": "Časové řady entity jsou povinné", "get-location": "Získat aktuální polohu", + "invalid-date": "Neplatné datum", "latitude": "Zeměpisná šířka", "longitude": "Zeměpisná délka", + "min-value-error": "Minimální hodnota je {{value}}", + "max-value-error": "Maximální hodnota je {{value}}", "not-allowed-entity": "Vybraná entita nemůže mít sdílené atributy", "no-attribute-selected": "Není vybrán žádný atribut", "no-datakey-selected": "Není vybrán žádný datový klíč", @@ -1900,7 +2445,10 @@ "no-entity-selected": "Není vybrána žádná entita", "no-image": "Žádný obrázek", "no-support-geolocation": "Váš prohlížeč nepodporuje geolokaci", - "no-support-web-camera": "Žádná podporovaná webová kamera", + "no-support-web-camera": "Váš prohlížeč nepodporuje kamery", + "enable-https-use-widget": "Prosím povolte HTTPS abyste mohli používat tento widget", + "no-found-your-camera": "Nelze nalézt vyši kameru", + "no-permission-camera": "Přístup byl zakázán uživatelem / Tato stránka nemá oprávnění použít kameru", "no-timeseries-selected": "Nejsou vybrány žádné časové řady", "secret-key": "Tajný klíč", "secret-key-required": "Tajný klíč je povinný", diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index 2df1635fd2..b65a875aed 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -2056,9 +2056,7 @@ "name-required": "Απαιτείται Όνομα", "configuration": "Διαμόρφωση", "schedule": "Πρόγραμμα", - "start": "Έναρξη", - "date": "Ημερομηνία", - "time": "Ώρα", + "start-time": "Ώρα έναρξης", "repeat": "Επανάληψη", "repeats": "Επαναλήψεις", "daily": "Καθημερινά", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index cb2d8e4f00..715302ff8c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -74,6 +74,7 @@ "admin": { "general": "General", "general-settings": "General Settings", + "home-settings": "Home Settings", "outgoing-mail": "Mail Server", "outgoing-mail-settings": "Outgoing Mail Server Settings", "system-settings": "System Settings", @@ -784,6 +785,10 @@ "hide-details": "Hide details", "select-state": "Select target state", "state-controller": "State controller", + "search": "Search dashboards", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected", + "home-dashboard": "Home dashboard", + "home-dashboard-hide-toolbar": "Hide home dashboard toolbar", "unassign-dashboard-from-edge-text": "After the confirmation the dashboard will be unassigned and won't be accessible by the edge.", "unassign-dashboards-from-edge-title": "Are you sure you want to unassign { count, plural, 1 {1 dashboard} other {# dashboards} }?", "unassign-dashboards-from-edge-text": "After the confirmation all selected dashboards will be unassigned and won't be accessible by the edge.", @@ -1131,6 +1136,10 @@ "schedule-time-to": "To", "schedule-days-of-week-required": "At least one day of week should be selected.", "lwm2m": { + "client-only-observe-after-connect": "{ count, plural, 1 {Only Observe Request to the client after registration} other {Read&Observe Request to the client after registration} }", + "client-only-observe-after-connect-tip": "{ count, plural, 1 {Only Observe Request to the client marked as observe from the profile configuration.} other {Read Request to the client after registration to read the values of the resources then Observe Request to the client marked as observe from the profile configuration.} }", + "client-update-value-after-connect": "{ count, plural, 1 {Request to the client after registration for All resource values} other {Request to the client after registration to read values only as attributes or telemetry} }", + "client-update-value-after-connect-tip": "{ count, plural, 1 {Request to the client after registration to read all resource values for all objects} other {Request to the client after registration to read the values of the resources marked as attribute or telemetry from the profile configuration.} }", "object-list": "Object list", "object-list-empty": "No objects selected.", "no-objects-matching": "No objects matching '{{object}}' were found.", @@ -1840,7 +1849,9 @@ "no-dynamic-value": "No dynamic value", "source-attribute": "Source attribute", "switch-to-dynamic-value": "Switch to dynamic value", - "switch-to-default-value": "Switch to default value" + "switch-to-default-value": "Switch to default value", + "inherit-owner": "Inherit from owner", + "source-attribute-not-set": "If source attribute isn't set" }, "fullscreen": { "expand": "Expand to fullscreen", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index f56cd60989..7c4394c026 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -4,9 +4,11 @@ "unauthorized-access": "Acceso no autorizado", "unauthorized-access-text": "Debes iniciar sesión para tener acceso a este recurso!", "access-forbidden": "Acceso Prohibido", - "access-forbidden-text": "No tienes derechos para acceder a esta ubicación!
Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.", + "access-forbidden-text": "No tienes suficientes derechos para acceder a esta ubicación!
Intenta iniciar sesión con otro usuario si todavía quieres acceder a esta ubicación.", "refresh-token-expired": "La sesión ha expirado", - "refresh-token-failed": "No se puede actualizar la sesión" + "refresh-token-failed": "No se puede actualizar la sesión", + "permission-denied": "Permiso Denegado", + "permission-denied-text": "No tienes suficientes derechos para realizar esta operación!" }, "action": { "activate": "Activar", @@ -21,6 +23,7 @@ "no": "No", "update": "Actualizar", "remove": "Eliminar", + "select": "Seleccionar", "search": "Buscar", "clear-search": "Borrar búsqueda", "assign": "Asignar", @@ -49,13 +52,16 @@ "import": "Importar", "export": "Exportar", "share-via": "Compartir vía {{provider}}", - "discard-changes": "Cancelar los cambios", "continue": "Continuar", - "download": "Descargar" + "discard-changes": "Cancelar cambios", + "download": "Descargar", + "next-with-label": "Siguiente: {{label}}", + "read-more": "Leer más", + "hide": "Ocultar" }, "aggregation": { - "aggregation": "Agregación", - "function": "Función de Agregación", + "aggregation": "Agrupación", + "function": "Función de Agrupación", "limit": "Valores Max", "group-interval": "Intervalo de agrupación", "min": "Min", @@ -74,6 +80,8 @@ "test-mail-sent": "Mail de prueba enviado correctamente!", "base-url": "URL Base", "base-url-required": "URL Base requerida.", + "prohibit-different-url": "Prohibir el uso de hostname en cabeceras de request del cliente", + "prohibit-different-url-hint": "Este ajuste debe ser activado en entornos de producción. Puede causar fallos de seguridad si está desactivado", "mail-from": "Mail Desde", "mail-from-required": "Mail Desde requerido.", "smtp-protocol": "Protocolo SMTP", @@ -87,9 +95,44 @@ "timeout-invalid": "No parece un Timeout valido.", "enable-tls": "Habilitar TLS", "tls-version": "Versión TLS", + "enable-proxy": "Habilitar proxy", + "proxy-host": "Host proxy", + "proxy-host-required": "Se requiere host Proxy.", + "proxy-port": "Puerto proxy", + "proxy-port-required": "Se requiere puerto proxy.", + "proxy-port-range": "El puerto proxy debe estar en un rango de 1 a 65535.", + "proxy-user": "Usuario proxy", + "proxy-password": "Contraseña proxy", "send-test-mail": "Enviar correo de prueba", - "password-policy": "Política de contraseñas", + "sms-provider": "Proveedor SMS", + "sms-provider-settings": "Ajustes proveedor SMS", + "sms-provider-type": "Tipo de proveedor SMS", + "sms-provider-type-required": "Se requiere proveedor SMS.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "aws-access-key-id": "AWS Access Key ID", + "aws-access-key-id-required": "Se requiere AWS Access Key ID", + "aws-secret-access-key": "AWS Secret Access Key", + "aws-secret-access-key-required": "Se requere AWS Secret Access Key", + "aws-region": "Región AWS", + "aws-region-required": "Se requere región AWS", + "number-from": "Nº de teléfono Origen", + "number-from-required": "Se requere Nº de teléfono origen.", + "number-to": "Nº de teléfono de destino", + "number-to-required": "Se requere Nº de teléfono de destino.", + "phone-number-hint": "Nº de teléfono en formato E.164, ex. +19995550123", + "phone-number-pattern": "Nº Inválido. Debe estar en formato E.164, ex. +19995550123.", + "sms-message": "Mensaje SMS", + "sms-message-required": "Se requeriere mensaje SMS.", + "sms-message-max-length": "Los SMS no pueden ser más largos de 1600 caracteres", + "twilio-account-sid": "SID de cuenta Twilio", + "twilio-account-sid-required": "Se requere SID de cuenta Twilio", + "twilio-account-token": "Token de cuenta Twilio", + "twilio-account-token-required": "Se requiere Token Twilio", + "send-test-sms": "Enviar SMS de prueba", + "test-sms-sent": "SMS enviado con éxito!", "security-settings": "Configuraciones de seguridad", + "password-policy": "Política de contraseñas", "minimum-password-length": "Longitud mínima de contraseña", "minimum-password-length-required": "Se requiere una longitud mínima de contraseña", "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 @@ "general-policy": "Política general", "max-failed-login-attempts": "Número máximo de intentos fallidos de inicio de sesión, antes de que la cuenta esté bloqueada", "minimum-max-failed-login-attempts-range": "El número máximo de intentos fallidos de inicio de sesión no puede ser negativo", - "user-lockout-notification-email": "En caso de bloqueo de la cuenta del usuario, envíe una notificación por correo electrónico" - }, + "user-lockout-notification-email": "En caso de bloqueo de la cuenta del usuario, envíe una notificación por correo electrónico", + "domain-name": "Nombre de dominio", + "domain-name-unique": "El nombre de dominio y protocolo debe ser único.", + "error-verification-url": "Un nombre de dominio no debe contener símbolos '/' y ':'. Ejemplo: thingsboard.io", + "oauth2": { + "access-token-uri": "URI Access token", + "access-token-uri-required": "Se requere URI Access token.", + "activate-user": "Activar usuario", + "add-domain": "Añadir dominio", + "delete-domain": "Borrar dominio", + "add-provider": "Añadir proveedor", + "delete-provider": "Borrar proveedor", + "allow-user-creation": "Permitir creación de usuario", + "always-fullscreen": "Siempre pantalla completa", + "authorization-uri": "URI Autorización", + "authorization-uri-required": "Se requiere URI de Autorización.", + "client-authentication-method": "Método de autenticación", + "client-id": "ID Cliente", + "client-id-required": "Se requere ID Cliente.", + "client-secret": "Secreto de Cliente", + "client-secret-required": "Se requiere Secreto de Cliente.", + "custom-setting": "Ajustes personalizados", + "customer-name-pattern": "Patrón nombre de cliente", + "default-dashboard-name": "Nombre de panel por defecto", + "delete-domain-text": "Atención, tras la confirmación el dominio y todos los datos del proveedor no estarán disponibles.", + "delete-domain-title": "Eliminar los ajustes del dominio '{{domainName}}'?", + "delete-registration-text": "Atención, tras la confirmación los datos del proveedor no estarán disponibles.", + "delete-registration-title": "Eliminar el proveedor '{{name}}'?", + "email-attribute-key": "Clave de atributos email", + "email-attribute-key-required": "Se requiere clave de atributos de email.", + "first-name-attribute-key": "Clave de atributos de nombre", + "general": "General", + "jwk-set-uri": "URI web key JSON", + "last-name-attribute-key": "Clave de atributos de apellido", + "login-button-icon": "Icono de botón login", + "login-button-label": "Etiqueta de proveedor", + "login-button-label-placeholder": "Login con $(Provider label)", + "login-button-label-required": "Clave de etiqueta requerida.", + "login-provider": "Proveedor de login", + "mapper": "Mapeador", + "new-domain": "Nuevo dominio", + "oauth2": "OAuth2", + "redirect-uri-template": "Plantilla de redirección URI", + "copy-redirect-uri": "Copiar URI de redirección", + "registration-id": "ID de registro", + "registration-id-required": "Se requiere ID de registro.", + "registration-id-unique": "El ID de registro debe ser único en el sistema.", + "scope": "Alcance", + "scope-required": "Se requiere alcance.", + "tenant-name-pattern": "Patrón de nombre de propietario", + "tenant-name-pattern-required": "Se requiere patrón de nombre de propietario.", + "tenant-name-strategy": "Estrategia de Nombre de Propietario", + "type": "Tipo de mapeador", + "uri-pattern-error": "Formato de URI inválido.", + "url": "URL", + "url-pattern": "Formato URL inválido.", + "url-required": "Se requiere URL.", + "user-info-uri": "URI Información de usuario", + "user-info-uri-required": "Se requiere URI de información usuario.", + "user-name-attribute-name": "Clave de atributos de nombre de usuario", + "user-name-attribute-name-required": "Se requiere clave de atributos de nombre de usuario", + "protocol": "Protocolo", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "Activar ajustes OAuth2" + } + }, "alarm": { "alarm": "Alarma", "alarms": "Alarmas", @@ -117,6 +226,8 @@ "no-alarms-matching": "No se han encontrado alarmas coincidentes con '{{entity}}' .", "alarm-required": "Alarma requerida", "alarm-status": "Estado de Alarma", + "alarm-status-list": "Lista de estados de Alarmas", + "any-status": "Cualquier estado", "search-status": { "ANY": "Todas", "ACTIVE": "Activas", @@ -143,6 +254,8 @@ "end-time": "Hora fin", "ack-time": "Hora de reconocimiento", "clear-time": "Hora de normalización", + "alarm-severity-list": "Lista de gravedad de alarmas", + "any-severity": "Cualquier gravedad", "severity-critical": "Crítica", "severity-major": "Mayor", "severity-minor": "Menor", @@ -158,19 +271,23 @@ "min-polling-interval-message": "El ciclo debe ser por lo menos de 1 segundo.", "aknowledge-alarms-title": "Reconocer { count, plural, 1 {1 alarma} other {# alarmas} }", "aknowledge-alarms-text": "Estas seguro de reconocer { count, plural, 1 {1 alarma} other {# alarmas} }?", - "aknowledge-alarm-title": "Recononcer Alarma", + "aknowledge-alarm-title": "Recononcer Alarma", "aknowledge-alarm-text": "Estas seguro de reconocer Alarma?", "clear-alarms-title": "Normalizar { count, plural, 1 {1 alarma} other {# alarmas} }", - "clear-alarms-text": "Estás seguro de limpiar { count, plural, 1 {1 alarma} other {# alarmas} }?", - "clear-alarm-title": "Limpiar Alarma", - "clear-alarm-text": "Estás seguro de limpiar Alarma?", - "alarm-status-filter": "Filtro de Alarmas", + "clear-alarms-text": "Limpiar { count, plural, 1 {1 alarma} other {# alarmas} }?", + "clear-alarm-title": "Limpiar Alarma", + "clear-alarm-text": "Limpiar Alarma?", + "alarm-status-filter": "Filtro de estados de Alarmas", + "alarm-filter": "Filtro de Alarmas", "max-count-load": "Número máximo de alarmas a cargar (0 - ilimitado)", "max-count-load-required": "Se requiere número máximo de alarmas.", "max-count-load-error-min": "El valor mínimo es 0.", "fetch-size": "Tamaño de búsqueda (Fetch)", "fetch-size-required": "Se requiere tamaño de búsqueda.", - "fetch-size-error-min": "El valor mínimo es 10." + "fetch-size-error-min": "El valor mínimo es 10.", + "alarm-type-list": "Lista de tipos de alarma", + "any-type": "Cualquier tipo", + "search-propagated-alarms": "Buscar alarmas propagadas" }, "alias": { "add": "Añadir alias", @@ -202,6 +319,7 @@ "filter-type-device-search-query-description": "Dispositivos con tipos {{deviceTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Consulta de búsqueda de vista de entidad", "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}}", + "filter-type-apiUsageState": "Uso de API", "filter-type-edge-search-query": "Consultar búsqueda de borde", "filter-type-edge-search-query-description": "Bordes con tipos {{edgeTypes}} que tienen {{relationType}} relación {{direction}} {{rootEntity}}", "type-assigned-to-edge": "Asignado a borde", @@ -267,19 +385,19 @@ "unassign-assets": "Cancelar asignación de activo", "unassign-assets-action-title": "Cancelar asignación de { count, plural, 1 {1 activo} other {# activos} } del cliente", "assign-new-asset": "Asignar nuevo activo", - "delete-asset-title": "Estás seguro de borrar el activo '{{assetName}}'?", + "delete-asset-title": "Eliminar el activo '{{assetName}}'?", "delete-asset-text": "Atención, tras la confirmación el activo y sus datos serán borrados e irrecuperables.", - "delete-assets-title": "Estás seguro de borrar los activos { count, plural, 1 {1 activo} other {# activos} }?", + "delete-assets-title": "Eliminar los activos { count, plural, 1 {1 activo} other {# activos} }?", "delete-assets-action-title": "Borrar { count, plural, 1 {1 activo} other {# activos} }", "delete-assets-text": "Atención, tras la confirmación todos los activos seleccionados y sus datos serán borrados e irrecuperables.", - "make-public-asset-title": "Estás seguro de hacer el activo '{{assetName}}' público?", + "make-public-asset-title": "Hacer el activo '{{assetName}}' público?", "make-public-asset-text": "Tras la confirmación, el activo y sus datos se harán públicos y accesibles por otros.", - "make-private-asset-title": "Estás seguro de hacer el activo '{{assetName}}' privado?", + "make-private-asset-title": "Hacer el activo '{{assetName}}' privado?", "make-private-asset-text": "Tras la confirmación, el activo y sus datos se harán privados y no serán accesibles por otros.", - "unassign-asset-title": "Estás seguro de cancelar la asignación del activo '{{assetName}}'?", + "unassign-asset-title": "Cancelar la asignación del activo '{{assetName}}'?", "unassign-asset-text": "Tras la confirmación, el activo será desasignado y no será accesible por el cliente.", "unassign-asset": "Cancelar asignación de activo", - "unassign-assets-title": "Estás seguro de cancelar las asignaciones { count, plural, 1 {1 activo} other {# activos} }?", + "unassign-assets-title": "Cancelar las asignaciones { count, plural, 1 {1 activo} other {# activos} }?", "unassign-assets-text": "Tras la confirmación todos los activos seleccionados serán desasignados y no serán accesibles por el cliente.", "copyId": "Copiar ID de activo", "idCopiedMessage": "El ID ha sido copiado al portapapeles", @@ -289,6 +407,8 @@ "name-starts-with": "El nombre de activo comienza con", "import": "Importar activos", "asset-file": "Archivo del activo", + "search": "Buscar activos", + "selected-assets": "{ count, plural, 1 {1 activo} other {# activos} } seleccionados", "label": "Etiqueta", "assign-asset-to-edge": "Asignar activo(s) al borde", "assign-asset-to-edge-text":"Por favor, seleccione los activos para asignar al borde", @@ -313,7 +433,7 @@ "key-required": "Clave del atributo requerida.", "value": "Valor", "value-required": "Valor del atributo requerido.", - "delete-attributes-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 atributo} other {# atributos} }?", + "delete-attributes-title": "¿Eliminar { count, plural, 1 {1 atributo} other {# atributos} }?", "delete-attributes-text": "Atención, tras la confirmación el atributo será eliminado, y la información relacionada será irrecuperable.", "delete-attributes": "Borrar atributo", "enter-attribute-value": "Ingresar valor del atributo", @@ -324,7 +444,62 @@ "add-to-dashboard": "Agregar al Panel", "add-widget-to-dashboard": "Agregar widget al Panel", "selected-attributes": "{ count, plural, 1 {1 atributo} other {# atributos} } seleccionados", - "selected-telemetry": "{ count, plural, 1 {1 telemetría} other {# telemetrías} } seleccionadas" + "selected-telemetry": "{ count, plural, 1 {1 telemetría} other {# telemetrías} } seleccionadas", + "no-attributes-text": "No se encontró ningún atributo", + "no-telemetry-text": "No se encontró ninguna telemetría" + }, + "api-usage": { + "api-usage": "Uso de API", + "data-points": "Puntos de datos", + "data-points-storage-days": "Días de grabación de puntos de datos", + "email": "Email", + "email-messages": "Mensajes de Email", + "email-messages-daily-activity": "Actividad diaria de Emails", + "email-messages-hourly-activity": "Actividad horaria de Emails", + "email-messages-monthly-activity": "Actividad mensual de Emails", + "exceptions": "Excepciones", + "executions": "Ejecuciones", + "javascript": "JavaScript", + "javascript-executions": "Ejecuciones JavaScript", + "javascript-functions": "Funciones JavaScript", + "javascript-functions-daily-activity": "Actividad diaria de funciones JavaScript", + "javascript-functions-hourly-activity": "Actividad horaria de funciones JavaScript", + "javascript-functions-monthly-activity": "Actividad mensual de funciones JavaScript", + "latest-error": "Último error", + "messages": "Mensajes", + "permanent-failures": "${entityName} Fallos permanentes", + "permanent-timeouts": "${entityName} Timeouts permanentes", + "processing-failures": "${entityName} Fallos de procesamiento", + "processing-failures-and-timeouts": "Fallos de procesamiento y timeouts", + "processing-timeouts": "${entityName} Timeouts de procesamiento", + "queue-stats": "Estadísticas de colas", + "rule-chain": "Cadena de reglas", + "rule-engine": "Motor de reglas", + "rule-engine-daily-activity": "Actividad diaria de motor de reglas", + "rule-engine-executions": "Ejecuciones de motor de reglas", + "rule-engine-hourly-activity": "Actividad horaria de motor de reglas", + "rule-engine-monthly-activity": "Actividad mensual de motor de reglas", + "rule-engine-statistics": "Estadisticas del motor de reglas", + "rule-node": "Nodo de reglas", + "sms": "SMS", + "sms-messages": "Mensajes SMS", + "sms-messages-daily-activity": "Actividad diaria de mensajes SMS", + "sms-messages-hourly-activity": "Actividad horaria de mensajes SMS", + "sms-messages-monthly-activity": "Actividad mensual de mensajes SMS", + "successful": "${entityName} Exitoso", + "telemetry": "Telemetría", + "telemetry-persistence": "Persistencia de telemetría", + "telemetry-persistence-daily-activity": "Actividad diaria de persistencia de telemetría", + "telemetry-persistence-hourly-activity": "Actividad horaria de persistencia de telemetría", + "telemetry-persistence-monthly-activity": "Actividad mensual de persistencia de telemetría", + "transport": "Transporte", + "transport-daily-activity": "Actividad diaria de transporte", + "transport-data-points": "Puntos de datos de transporte", + "transport-hourly-activity": "Actividad horaria de transporte", + "transport-messages": "Mensajes de transporte", + "transport-monthly-activity": "Actividad mensual de transporte", + "view-details": "Ver detalles", + "view-statistics": "Ver estadísticas" }, "audit-log": { "audit": "Auditoría", @@ -364,11 +539,17 @@ "action-data": "Datos de acción", "failure-details": "Detalles del error", "search": "Buscar registros de auditoría", - "clear-search": "Borrar búsqueda" + "clear-search": "Borrar búsqueda", + "type-assigned-from-tenant": "Asignado desde el administrador", + "type-assigned-to-tenant": "Asignado al administrador", + "type-provision-success": "Dispositivo aprovisionado", + "type-provision-failure": "Aprovisionamiento fallido", + "type-timeseries-updated": "Telemetría actualizada", + "type-timeseries-deleted": "Telemetría borrada" }, "confirm-on-exit": { - "message": "Tienes cambios sin guardar. ¿Estás seguro que quieres abandonar la página?", - "html-message": "Tienes cambios sin guardar.
¿Estás seguro que quieres abandonar la página?", + "message": "Tienes cambios sin guardar. ¿Abandonar la página?", + "html-message": "Tienes cambios sin guardar.
¿Abandonar la página?", "title": "Cambios sin guardar" }, "contact": { @@ -388,7 +569,9 @@ "password": "Contraseña", "enter-username": "Introduce el nombre de usuario.", "enter-password": "Introduce la contraseña", - "enter-search": "Introduce búsqueda" + "enter-search": "Introduce búsqueda", + "created-time": "Fecha de creación", + "loading": "Cargando..." }, "content-type": { "json": "Json", @@ -423,9 +606,9 @@ "add-customer-text": "Agregar nuevo cliente", "no-customers-text": "No se encontraron clientes", "customer-details": "Detalles del cliente", - "delete-customer-title": "¿Estás seguro que quieres eliminar el cliente '{{customerTitle}}'?", + "delete-customer-title": "¿Eliminar el cliente '{{customerTitle}}'?", "delete-customer-text": "Atención, tras la confirmación el cliente será eliminado y toda la información relacionada será irrecuperable.", - "delete-customers-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 cliente} other {# clientes} }?", + "delete-customers-title": "¿Eliminar { count, plural, 1 {1 cliente} other {# clientes} }?", "delete-customers-action-title": "Borrar { count, plural, 1 {1 cliente} other {# clientes} }", "delete-customers-text": "Atención, tras la confirmación todos los clientes seleccionados serán eliminados y su información relacionada será irrecuperable.", "manage-users": "Gestionar usuarios", @@ -445,7 +628,9 @@ "customer-required": "Cliente requerido", "select-default-customer": "Seleccionar cliente por defecto", "default-customer": "Cliente por defecto", - "default-customer-required": "Se requiere cliente por defecto para realizar debug a nivel de propietario" + "default-customer-required": "Se requiere cliente por defecto para realizar debu a nivel de propietario", + "search": "Buscar clientes", + "selected-customers": "{ count, plural, 1 {1 cliente} other {# clientes} } seleccionados" }, "datetime": { "date-from": "Fecha desde", @@ -491,20 +676,20 @@ "delete-dashboards": "Eliminar paneles", "unassign-dashboards": "Desasignar paneles", "unassign-dashboards-action-title": "Desasignar { count, plural, 1 {1 paneles} other {# paneles} } del cliente", - "delete-dashboard-title": "¿Estás seguro que quieres eliminar el panel '{{dashboardTitle}}'?", + "delete-dashboard-title": "¿Eliminar el panel '{{dashboardTitle}}'?", "delete-dashboard-text": "Atención, el panel seleccionado será eliminado y la información relacionada sera irrecuperable.", - "delete-dashboards-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 panel} other {# paneles} }?", + "delete-dashboards-title": "¿Eliminar { count, plural, 1 {1 panel} other {# paneles} }?", "delete-dashboards-action-title": "Eliminar { count, plural, 1 {1 panel} other {# paneles} }", "delete-dashboards-text": "Atención, los paneles seleccionados serán eliminados y la información relacionada será irrecuperable.", - "unassign-dashboard-title": "¿Estás seguro que quieres desasignar el panel '{{dashboardTitle}}'?", + "unassign-dashboard-title": "¿Desasignar el panel '{{dashboardTitle}}'?", "unassign-dashboard-text": "Tras la confirmación, el panel será desasignado y no podrá ser accesible por el cliente.", "unassign-dashboard": "Desasignar panel", - "unassign-dashboards-title": "¿Estás seguro que quieres desasignar { count, plural, 1 {1 panel} other {# paneles} }?", + "unassign-dashboards-title": "¿Desasignar { count, plural, 1 {1 panel} other {# paneles} }?", "unassign-dashboards-text": "Atención, tras la confirmación los paneles seleccionados serán desasignados y no podrán ser accesibles por el cliente.", "public-dashboard-title": "El panel ahora es público", "public-dashboard-text": "Tu panel {{dashboardTitle}} es ahora público y podrá ser accedido desde: aquí:", "public-dashboard-notice": "Nota: No olvides hacer públicos los dispositivos relacionados para acceder a sus datos.", - "make-private-dashboard-title": "¿Estás seguro que quieres hacer el panel '{{dashboardTitle}}' privado?", + "make-private-dashboard-title": "¿Hacer el panel '{{dashboardTitle}}' privado?", "make-private-dashboard-text": "Tras la confirmación, el panel será privado y no podrá ser accesible por otros.", "make-private-dashboard": "Hacer panel privado", "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard", @@ -528,6 +713,9 @@ "min-columns-count-message": "Solo se permite un número mínimo de 10 columnas.", "max-columns-count-message": "Solo se permite un número máximo de 1000 columnas.", "widgets-margins": "Margen entre widgets", + "margin-required": "Valor de margen requerido.", + "min-margin-message": "0 es el valor de margen mínimo permitido.", + "max-margin-message": "50 es el valor de margen máximo permitido.", "horizontal-margin": "Margen horizontal", "horizontal-margin-required": "Margen horizontal requerido.", "min-horizontal-margin-message": "Solo se permite margen horizontal mínimo de 0.", @@ -547,6 +735,7 @@ "title-color": "Color del título", "display-dashboards-selection": "Mostrar selección de paneles", "display-entities-selection": "Mostrar selección de entidades", + "display-filters": "Mostrar filtros", "display-dashboard-timewindow": "Mostrar ventana de tiempo", "display-dashboard-export": "Mostrar exportar", "import": "Importar panel", @@ -580,6 +769,7 @@ "edit-state": "Editar estado panel", "delete-state": "Borrar estado panel", "add-state": "Añadir estado panel", + "no-states-text": "No se han encontrado estados", "state": "Estado de panel", "state-name": "Nombre", "state-name-required": "Se requiere nombre del estado.", @@ -588,11 +778,13 @@ "state-id-exists": "Ya existe un ID de estado.", "is-root-state": "Estado raiz(Root)", "delete-state-title": "Borrar estado de panel", - "delete-state-text": "Estás seguro de eliminar el estado de panel con nombre: '{{stateName}}'?", + "delete-state-text": "Eliminar el estado de panel con nombre: '{{stateName}}'?", "show-details": "Mostrar detalles", "hide-details": "Ocultar detalles", "select-state": "Seleccionar estado destino (target state)", "state-controller": "Controlador de estados", + "search": "Buscar paneles", + "selected-dashboards": "{ count, plural, 1 {1 panel} other {# paneles} } seleccionados", "unassign-dashboard-from-edge-text": "Después de la confirmación, el tablero no será asignado y el borde no podrá acceder a él", "unassign-dashboards-from-edge-text": "Después de la confirmación, se anulará la asignación de todos los paneles seleccionados y no serán accesibles por de borde", "assign-dashboard-to-edge": "Asignar panel(es) al borde", @@ -614,6 +806,7 @@ "alarm": "Campos de alarma", "timeseries-required": "Series de tiempo del dispositivo requerido.", "timeseries-or-attributes-required": "Series de tiempo/Atributos requeridos.", + "alarm-fields-timeseries-or-attributes-required": "Se requieren campos de alarma o series de tiempo/atributos.", "maximum-timeseries-or-attributes": "Máximo { count, plural, 1 {1 timeseries/atributo es permitido.} other {# timeseries/atributos son permitidos} }", "alarm-fields-required": "Campos de alarma requeridos.", "function-types": "Tipos de funciones", @@ -631,6 +824,7 @@ "add-datasource-prompt": "Por favor, agrega una fuente de datos" }, "details": { + "details": "Detalles", "edit-mode": "Modo Edición", "edit-json": "Editar JSON", "toggle-edit-mode": "Ir a Modo Edición" @@ -682,20 +876,20 @@ "unassign-devices": "Desasignar dispositivos", "unassign-devices-action-title": "Desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} } del cliente", "assign-new-device": "Asignar nuevo dispositivo", - "make-public-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' público?", + "make-public-device-title": "¿Hacer el dispositivo '{{deviceName}}' público?", "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.", - "make-private-device-title": "¿Estás seguro que quieres hacer el dispositivo '{{deviceName}}' privado?", + "make-private-device-title": "¿Hacer el dispositivo '{{deviceName}}' privado?", "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.", "view-credentials": "Ver credenciales", - "delete-device-title": "¿Estás seguro que quieres eliminar el dispositivo '{{deviceName}}'?", + "delete-device-title": "¿Eliminar el dispositivo '{{deviceName}}'?", "delete-device-text": "Atención, tras la confirmación los dispositivos serán eliminados y la información relacionada será irrecuperable.", - "delete-devices-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", + "delete-devices-title": "¿Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", "delete-devices-action-title": "Eliminar { count, plural, 1 {1 dispositivo} other {# dispositivos} }", "delete-devices-text": "Atención, tras la confirmación los dispositivos seleccionados serán eliminados y la información relacionada será irrecuperable.", - "unassign-device-title": "¿Estás seguro que quieres desasignar el dispositivo '{{deviceName}}'?", + "unassign-device-title": "¿Desasignar el dispositivo '{{deviceName}}'?", "unassign-device-text": "Tras la confirmación, el dispositivo será desasignado y no podrá ser accesible por el cliente.", "unassign-device": "Desasignar dispositivo", - "unassign-devices-title": "¿Estás seguro que quieres desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", + "unassign-devices-title": "¿Desasignar { count, plural, 1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-text": "Tras la confirmación, los dispositivos seleccionados serán desasignados y no podrán ser accedidos por el cliente.", "device-credentials": "Credenciales del dispositivo", "credentials-type": "Tipo de credencial", @@ -704,6 +898,12 @@ "access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.", "rsa-key": "Clave pública RSA", "rsa-key-required": "Clave pública RSA requerida.", + "client-id": "ID Cliente", + "client-id-pattern": "Contiene carácter inválido.", + "user-name": "Nombre Usuario", + "user-name-required": "Se requiere nombre de usuario.", + "client-id-or-user-name-necessary": "El ID Cliente y/o el Nombre de usuario son necesarios", + "password": "Contraseña", "secret": "Secreta", "secret-required": "Secreta requerida.", "device-type": "Tipo de dispositivo", @@ -716,30 +916,196 @@ "device-types": "Tipos de dispositivo", "name": "Nombre", "name-required": "El nombre es requerido.", - "label": "Etiqueta", "description": "Descripción", + "label": "Etiqueta", "events": "Eventos", "details": "Detalles", "copyId": "Copiar ID", "copyAccessToken": "Copiar access token", + "copy-mqtt-authentication": "Copiar credenciales MQTT", "idCopiedMessage": "Id del dispositivo copiado al portapapeles", "accessTokenCopiedMessage": "Access token del dispositivo copiado al portapapeles", + "mqtt-authentication-copied-message": "Los datos de autenticación MQTT se han copiado al portapapeles", "assignedToCustomer": "Asignado al cliente", "unable-delete-device-alias-title": "Imposible eliminar alias del dispositivo", "unable-delete-device-alias-text": "Alias '{{deviceAlias}}' no puede ser eliminado. Esta siendo usado por el(los) widget(s):
{{widgetsList}}", "is-gateway": "Es gateway", + "overwrite-activity-time": "Sobreescribir hora de actividad para el dispositivo conectado", "public": "Público", "device-public": "El dispositivo es público", "select-device": "Seleccionar dispositivo", - "device-file": "Archivo de dispositivo", "import": "Importar dispositivo", + "device-file": "Archivo de dispositivo", + "search": "Buscar dispositivos", + "selected-devices": "{ count, plural, 1 {1 dispositivo} other {# dispositivos} } seleccionados", + "device-configuration": "Configuración del dispositivo", + "transport-configuration": "Configuración del transporte", + "wizard": { + "device-wizard": "Asistente de dispositivo", + "device-details": "Detalles del dispositivo", + "new-device-profile": "Crear un nuevo perfil de dispositivo", + "existing-device-profile": "Seleccionar un perfil existente", + "specific-configuration": "Configuración específica", + "customer-to-assign-device": "Cliente al que asignar el dispositivo", + "add-credential": "Añadir credencial" + }, "assign-device-to-edge-text": "Seleccione los dispositivos para asignar al borde", "unassign-device-from-edge-title": "¿Está seguro de que desea desasignar el dispositivo '{{deviceName}}'?", "unassign-device-from-edge-text": "Después de la confirmación, el dispositivo no será asignado y el borde no podrá acceder a él", "unassign-devices-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 dispositivo} other {# dispositivos}}?", "unassign-devices-from-edge-text": "Después de la confirmación, todos los dispositivos seleccionados quedarán sin asignar y el borde no podrá acceder a ellos." }, - "dialog": { + "device-profile": { + "device-profile": "Perfil de dispositivo", + "device-profiles": "Perfiles de dispositivo", + "all-device-profiles": "Todos", + "add": "Añadir perfil de dispositivo", + "edit": "Editar perfil de dispositivo", + "device-profile-details": "Detalles de perfil de dispositivo", + "no-device-profiles-text": "No se encontraron perfiles", + "search": "Buscar perfiles", + "selected-device-profiles": "{ count, plural, 1 {1 perfil} other {# perfiles} } seleccionados", + "no-device-profiles-matching": "No existe perfil que conincida con '{{entity}}'.", + "device-profile-required": "Se requiere perfil de dispositivo", + "idCopiedMessage": "Se ha copiado el ID de perfil al portapapeles", + "set-default": "Hacer perfil por defecto", + "delete": "Borrar perfil de dispositivo", + "copyId": "Copiar ID de perfil", + "new-device-profile-name": "Nombre de perfil", + "new-device-profile-name-required": "Se requiere nombre de perfil.", + "name": "Nombre", + "name-required": "Se requiere nombre.", + "type": "Tipo de perfil", + "type-required": "Se requiere tipo de perfil.", + "type-default": "Por defecto", + "transport-type": "Tipo de transporte", + "transport-type-required": "Se requiere tipo de transporte.", + "transport-type-default": "Por defecto", + "transport-type-default-hint": "Soporta transportes por MQTT básico, HTTP y CoAP", + "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "Activa ajustes avanzados de transporte MQTT", + "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "Transporte LWM2M", + "description": "Descripción", + "default": "Defecto", + "profile-configuration": "Configuración de perfil", + "transport-configuration": "Configuración de transporte", + "default-rule-chain": "Cadena de reglas por defecto", + "select-queue-hint": "Selecciona desde el desplegable o añade un nombre personalizado.", + "delete-device-profile-title": "Eliminar el perfil '{{deviceProfileName}}'?", + "delete-device-profile-text": "Atención, tras la confirmación el perfil y todos sus datos serán borrados e irrecuperables.", + "delete-device-profiles-title": "EEliminar { count, plural, 1 {1 perfil} other {# perfiles} }?", + "delete-device-profiles-text": "Atención, tras la confirmación los perfiles seleccionados y todos sus datos serán borrados e irrecuperables.", + "set-default-device-profile-title": "Establecer el perfil '{{deviceProfileName}}' como perfil por defecto?", + "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.", + "no-device-profiles-found": "No se encontraron perfiles.", + "create-new-device-profile": "Crear un nuevo perfil!", + "mqtt-device-topic-filters": "Filtros de topic MQTT", + "mqtt-device-topic-filters-unique": "Los filtros de topic de dispositivo MQTT deben ser únicos.", + "mqtt-device-payload-type": "Payload de dispositivo MQTT", + "mqtt-device-payload-type-json": "JSON", + "mqtt-device-payload-type-proto": "Protobuf", + "mqtt-payload-type-required": "Se requiere tipo de Payload.", + "support-level-wildcards": "Se soportan los wilcards únicos [+] y multi-nivel [#].", + "telemetry-topic-filter": "Filtro de topic en telemetría", + "telemetry-topic-filter-required": "Se requiere filtro de topic (telemetría).", + "attributes-topic-filter": "Filtro de topic en atributos", + "attributes-topic-filter-required": "Se requiere filtro de topic (atributos).", + "telemetry-proto-schema": "Proto schema de telemetría", + "telemetry-proto-schema-required": "Se requiere proto schema de telemetría.", + "attributes-proto-schema": "Proto schema de atributos", + "attributes-proto-schema-required": "Se requiere proto schema de atributos.", + "rpc-response-topic-filter": "Filtro de topic de respuesta RPC", + "rpc-response-topic-filter-required": "Se requiere fitro de respuesta RPC.", + "not-valid-pattern-topic-filter": "No es un patrón de filtro válido", + "not-valid-single-character": "Uso inválido de wildcard único", + "not-valid-multi-character": "Uso inválido de wildcard multi-nivel", + "single-level-wildcards-hint": "[+] es adecuado para cualquier nivel. Ej.: v1/devices/+/telemetry o +/devices/+/attributes.", + "multi-level-wildcards-hint": "[#] puede reemplazar el mismo filtro y debe ser el último símbolo del topic. Ej.: # o v1/devices/me/#.", + "alarm-rules": "Reglas de alarma", + "alarm-rules-with-count": "Reglas de alarma ({{count}})", + "no-alarm-rules": "No hay reglas de alarma configuradas", + "add-alarm-rule": "Añadir regla de alarma", + "edit-alarm-rule": "Editar regla de alarma", + "alarm-type": "Tipo de alarma", + "alarm-type-required": "Se requiere tipo de alarma.", + "alarm-type-unique": "El tipo de alarma, debe ser único dentro de las reglas de alarma del perfil de dispositivo.", + "create-alarm-pattern": "Crear alarma {{alarmType}}", + "create-alarm-rules": "Crear reglas de alarma", + "no-create-alarm-rules": "No hay condiciones de creación de alarma configuradas", + "add-create-alarm-rule-prompt": "Por favor, añade una regla de alarma", + "clear-alarm-rule": "Borrar regla de alarma", + "no-clear-alarm-rule": "No hay condiciones de borrado de alarma configuradas", + "add-create-alarm-rule": "Añadir crear condición (activar alarma)", + "add-clear-alarm-rule": "Añair borrar condición (limpiar alarma)", + "select-alarm-severity": "Selecciona severidad de alarma", + "alarm-severity-required": "Se requiere especificar severidad de alarma.", + "condition-duration": "Duración de condición", + "condition-duration-value": "Valor de duración", + "condition-duration-time-unit": "Unidad de tiempo", + "condition-duration-value-range": "El valor debe estar en un rango desde 1 a 2147483647.", + "condition-duration-value-pattern": "El valor de duración debe ser un número entero.", + "condition-duration-value-required": "Se requiere valor de duración.", + "condition-duration-time-unit-required": "Se requiere una unidad de tiempo.", + "advanced-settings": "Ajustes avanzados", + "alarm-rule-details": "Detalles", + "add-alarm-rule-details": "Añadir detalles", + "propagate-alarm": "Propagar alarma", + "alarm-rule-relation-types-list": "Tipos de relación para propagar", + "alarm-rule-relation-types-list-hint": "Si no está seleccionado 'propagar relaciones', las alarmas serán propagadas sin filtrar por relación.", + "alarm-details": "Detalles de alarma", + "alarm-rule-condition": "Condiciones de regla de alarma", + "enter-alarm-rule-condition-prompt": "Por favor, añade una condición de alarma", + "edit-alarm-rule-condition": "Editar condición de alarma", + "device-provisioning": "Aprovisionamiento de dispositivos", + "provision-strategy": "Estrategia de aprovisionamiento", + "provision-strategy-required": "Se requiere estrategia de aprovisionamiento.", + "provision-strategy-disabled": "Desactivado", + "provision-strategy-created-new": "Permitir crear nuevos dispositivos", + "provision-strategy-check-pre-provisioned": "Revisar dispositivos pre-aprovisionados", + "provision-device-key": "Clave de aprovisionamiento", + "provision-device-key-required": "Se requiere clave de aprovisionamiento.", + "copy-provision-key": "Copiar clave de aprovisionamiento", + "provision-key-copied-message": "La clave de aprovisionamiento se ha copiado al portapapeles", + "provision-device-secret": "Secreto de aprovisionamiento", + "provision-device-secret-required": "Se requiere secreto de aprovisionamiento.", + "copy-provision-secret": "Copiar secreto de aprovisionamiento", + "provision-secret-copied-message": "Se ha copiado el secreto de aprovisionamiento al portapapeles", + "condition": "Condición", + "condition-type": "Tipo de condición", + "condition-type-simple": "Simple", + "condition-type-duration": "Duración", + "condition-during": "Durante {{during}}", + "condition-type-repeating": "Repetitiva", + "condition-type-required": "Se requiere tipo de condición.", + "condition-repeating-value": "Nº de eventos", + "condition-repeating-value-range": "El Nº de eventos debe estar en un rango de 1 to 2147483647.", + "condition-repeating-value-pattern": "Nº de eventos debe ser un número entero.", + "condition-repeating-value-required": "Se requiere Nº de eventos.", + "condition-repeat-times": "Repetición { count, plural, 1 {1 vez} other {# veces} }", + "schedule-type": "Tipo de horario", + "schedule-type-required": "Tipo de horario requerido.", + "schedule": "Horario", + "edit-schedule": "Editar horario de alarma", + "schedule-any-time": "Siempre activo", + "schedule-specific-time": "Activo en una hora específica", + "schedule-custom": "Personalizado", + "schedule-day": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo" + }, + "schedule-days": "Días", + "schedule-time": "Hora", + "schedule-time-from": "De", + "schedule-time-to": "Hasta", + "schedule-days-of-week-required": "Debe ser seleccionado por lo menos un día de la semana." + }, + "dialog": { "close": "Cerrar diálogo" }, "direction": { @@ -942,6 +1308,10 @@ "type-devices": "Dispositivos", "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista de # Dispositivos} }", "device-name-starts-with": "Dispositivos cuyos nombres comiencen por '{{prefix}}'", + "type-device-profile": "Perfil de dispositivo", + "type-device-profiles": "Perfiles de dispositivo", + "list-of-device-profiles": "{ count, plural, 1 {un perfil} other {Lista de # perfiles} }", + "device-profile-name-starts-with": "Perfiles cuyo nombre empiece por '{{prefix}}'", "type-asset": "Activo", "type-assets": "Activos", "list-of-assets": "{ count, plural, 1 {Un activo} other {Lista de # activos} }", @@ -961,7 +1331,11 @@ "type-tenant": "Propietario", "type-tenants": "Propietarios", "list-of-tenants": "{ count, plural, 1 {Un propietario} other {Lista de # propietarios} }", - "tenant-name-starts-with": "Tenants cuyos nombres comiencen por '{{prefix}}'", + "tenant-name-starts-with": "Propietarios cuyo nombre comience por '{{prefix}}'", + "type-tenant-profile": "Perfil de Propietario", + "type-tenant-profiles": "Perfiles de propietario", + "list-of-tenant-profiles": "{ count, plural, 1 {Un perfil de propietario} other {Lista de # perfiles de propietario} }", + "tenant-profile-name-starts-with": "Pefiles de propietario cuyo nombre empiece por '{{prefix}}'", "type-customer": "Cliente", "type-customers": "Clientes", "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista de # clientes} }", @@ -988,6 +1362,8 @@ "rulenode-name-starts-with": "Nodos de reglas cuyos nombres comienzan con '{{prefix}}'", "type-current-customer": "Cliente Actual", "type-current-tenant": "Propietario Actual", + "type-current-user": "Usuario Actual", + "type-current-user-owner": "Usuario Propietario Actual", "search": "Buscar entidades", "selected-entities": "{ count, plural, 1 {1 entidad} other {# entidades} } seleccionadas", "entity-label": "Etiqueta de entidad", @@ -996,13 +1372,14 @@ "no-entities-prompt": "No se han encontrado entidades", "no-data": "No hay datos que mostrar", "columns-to-display": "Columnas a Mostrar", + "type-api-usage-state": "Estado de uso de la API", "type-edge": "Borde", "type-edges": "Bordes", "list-of-edges": "{cuenta, plural, 1 {Un borde} other {Lista de # bordes}}", "edge-name-starts-with": "Bordes cuyos nombres comienzan con '{{prefijo}}'" }, "entity-field": { - "created-time": "Tiempo de creación", + "created-time": "Hora de creación", "name": "Nombre", "type": "Tipo", "first-name": "Nombre", @@ -1035,6 +1412,7 @@ "duplicate-alias-error": "Alias duplicado'{{alias}}'.
Los alias de Entity View deben ser únicos en el panel.", "configure-alias": "Configurar alias '{{alias}}'", "no-entity-views-matching": "No se encontraron vistas que coincidan con '{{entity}}'.", + "public": "Público", "alias": "Alias", "alias-required": "Alias de vista de entidad es requerido.", "remove-alias": "Borrar alias de la vista de entidad", @@ -1046,6 +1424,7 @@ "entity-view-name-filter-required": "Nombre del filtro de vista de entidad es requerido.", "entity-view-name-filter-no-entity-view-matched": "No se encontraron vistas de entidad que comiencen con '{{entityView}}'.", "add": "Añadir vista de entidad", + "entity-view-public": "Vista de entidad es pública", "assign-to-customer": "Asignar a cliente", "assign-entity-view-to-customer": "Asignar vista de entidad a cliente", "assign-entity-view-to-customer-text": "Por favor, seleccione las vistas de entidad para asignar al cliente", @@ -1062,15 +1441,15 @@ "unassign-entity-views-action-title": "Anular asignación { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } al cliente", "assign-new-entity-view": "Asignar nueva vista de entidad", "delete-entity-view-title": "¿Está seguro que quiere borrar la vista de entidad '{{entityViewName}}'?", - "delete-entity-view-text": "¡Cuidado! Después de la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.", + "delete-entity-view-text": "¡Cuidado! Tras la confirmación, la vista de la entidad y todos los datos relacionados serán irrecuperables.", "delete-entity-views-title": "¿Está seguro que quiere borrar las vistas de entidad { count, plural, 1 {1 entityView} other {# entityViews} }?", "delete-entity-views-action-title": "Borrar { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }", - "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.", + "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.", "unassign-entity-view-title": "¿Está seguro que quiere anular la asignación de la vista de entidad '{{entityViewName}}'?", - "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.", + "unassign-entity-view-text": "Tras la confirmación, la vista de la entidad quedará sin asignar y el cliente no podrá acceder a ella.", "unassign-entity-view": "Anular asignación de la vista de entidad", "unassign-entity-views-title": "¿Está seguro que quiere anular la asignación de { count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }?", - "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.", + "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.", "entity-view-type": "Tipo de vista de entidad", "entity-view-type-required": "Tipo de vista de entidad es requerido.", "select-entity-view-type": "Seleccione el tipo de vista de entidad", @@ -1079,12 +1458,14 @@ "no-entity-view-types-matching": "No se encontraron tipos de vista de entidad que coincidan con '{{entitySubtype}}'.", "entity-view-type-list-empty": "No hay tipos de vista de entidad seleccionados.", "entity-view-types": "Tipos de vista de entidad", + "created-time": "Fecha de creación", "name": "Nombre", "name-required": "Nombre Requerido.", "description": "Descripción", "events": "Eventos", "details": "Detalles", "copyId": "Copiar el Id de la vista de entidad", + "idCopiedMessage": "El Id de la vista de entidad se ha copiado al portapapeles", "assignedToCustomer": "Asignado a cliente", "unable-entity-view-device-alias-title": "No se puede eliminar el alias de vista de entidad", "unable-entity-view-device-alias-text": "El alias del dispositivo '{{entityViewAlias}}' no se puede borrar porque está siendo usado por el widget(s):
{{widgetsList}}", @@ -1110,9 +1491,11 @@ "timeseries-data": "Datos de series temporales", "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.", "make-public-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea pública?", - "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.", + "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.", "make-private-entity-view-title": "¿Está seguro de que desea que la vista de entidad '{{entityViewName}}' sea privada?", - "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.", + "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.", + "search": "Buscar vistas de entidad", + "selected-entity-views": "{ count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } seleccionadas", "assign-entity-view-to-edge": "Asignar vista (s) de entidad a borde", "assign-entity-view-to-edge-text": "Seleccione las vistas de entidad para asignar al borde", "unassign-entity-view-from-edge-title": "¿Está seguro de que desea anular la asignación de la vista de entidad '{{entityViewName}}'?", @@ -1154,7 +1537,7 @@ }, "extension": { "extensions": "Extensiones", - "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } seleccionadas", + "selected-extensions": "{ count, plural, 1 {1 extensión} other {# extensiones} } seleccionadas", "type": "Tipo", "key": "Clave", "value": "Valor", @@ -1166,9 +1549,9 @@ "delete": "Borrar Extensión", "add": "Añadir Extensión", "edit": "Editar Extensión", - "delete-extension-title": "Estás seguro de borrar la extensión '{{extensionId}}'?", + "delete-extension-title": "Eliminar la extensión '{{extensionId}}'?", "delete-extension-text": "Atención, tras la confirmación la extensión y sus datos serán borrados e irrecuperables.", - "delete-extensions-title": "Estás seguro de borrar las extensiones { count, plural, 1 {1 extensión} other {# extensiones} }?", + "delete-extensions-title": "Eliminar las extensiones { count, plural, 1 {1 extensión} other {# extensiones} }?", "delete-extensions-text": "Atención, tras la confirmación todas las extensiones seleccionadas y sus datos serán borrados e irrecuperables.", "converters": "Convertidores", "converter-id": "Id de convertidor", @@ -1309,6 +1692,93 @@ "file": "Fichero de extensiones", "invalid-file-error": "Fichero de extensiones inválido" }, + "filter": { + "add": "Añadir filtro", + "edit": "Editar filtro", + "name": "Nombre de filtro", + "name-required": "Se requiere nombre de filtro.", + "duplicate-filter": "Ya existe un filtro con el mismo nombre.", + "filters": "Filtros", + "unable-delete-filter-title": "Error borrando filtro", + "unable-delete-filter-text": "El filtro '{{filter}}' no puede ser borrado debido a que está siendo usado actualmente por los siguientes widgets:
{{widgetsList}}", + "duplicate-filter-error": "Se ha encontrado un filtro duplicado '{{filter}}'.
Los filtros deben ser únicos en el panel.", + "missing-key-filters-error": "No se encontró la clave de filtros para el filtro '{{filter}}'.", + "filter": "Filtro", + "editable": "Editable", + "no-filters-found": "No se encontraron filtros.", + "no-filter-text": "No se ha especificado filtro", + "add-filter-prompt": "Por favos, añadir filtro", + "no-filter-matching": "'{{filter}}' no encontrado.", + "create-new-filter": "Crear un filtro nuevo!", + "filter-required": "Se requiere filtro.", + "operation": { + "operation": "Operación", + "equal": "igual", + "not-equal": "no igual", + "starts-with": "comienza con", + "ends-with": "acaba con", + "contains": "contiene", + "not-contains": "no contiene", + "greater": "mayor que", + "less": "menor que", + "greater-or-equal": "mayor o igual", + "less-or-equal": "menor o igual", + "and": "y", + "or": "o" + }, + "ignore-case": "Ignorar mayús/minus", + "value": "Valor", + "remove-filter": "Borrar filtro", + "preview": "Vista previa de filtro", + "no-filters": "No hay filtros configurados", + "add-filter": "Añadir filtro", + "add-complex-filter": "Añadir filtro complejo", + "add-complex": "Agregar filtro complejo", + "complex-filter": "Filtro complejo", + "edit-complex-filter": "Editar filtro complejo", + "edit-filter-user-params": "Editar parámetros de usuario del filtro", + "filter-user-params": "Filtro de parámetros de usuario (predicado)", + "user-parameters": "Parámetros de usuario", + "display-label": "Etiqueta a mostrar", + "autogenerated-label": "Auto generar etiqueta", + "order-priority": "Prioridad orden de campos", + "key-filter": "Filtros (clave)", + "key-filters": "Filtros (claves)", + "key-name": "Nombre de clave", + "key-name-required": "Se requiere nombre de clave.", + "key-type": { + "key-type": "Tipo de clave", + "attribute": "Atributo", + "timeseries": "Timeseries", + "entity-field": "Campo de entidad" + }, + "value-type": { + "value-type": "Tipo de valor", + "string": "Cadena", + "numeric": "Numerico", + "boolean": "Booleano", + "date-time": "Fecha/Hora" + }, + "value-type-required": "Se requiere tipo de valor.", + "key-value-type-change-title": "Cambiar el tipo de valor de la clave?", + "key-value-type-change-message": "Si confirmas el nuevo tipo, todos los filtros se borrarán.", + "no-key-filters": "No hay filtros claves configurados", + "add-key-filter": "Añadir filtro clave", + "remove-key-filter": "Borrar filtro clave", + "edit-key-filter": "Editar filtro clave", + "date": "Fecha", + "time": "Hora", + "current-tenant": "Admin actual", + "current-customer": "Cliente actual", + "current-user": "Usuario actual", + "current-device": "Dispositivo actual", + "default-value": "Valor por defecto", + "dynamic-source-type": "Tipo de origen dinámico", + "no-dynamic-value": "Sin valor dinámico", + "source-attribute": "Atributo de origen", + "switch-to-dynamic-value": "Cambiar a valor dinámico", + "switch-to-default-value": "Cambiar a valor por defecto" + }, "fullscreen": { "expand": "Expandir a Pantalla Completa", "exit": "Salir de Pantalla Completa", @@ -1328,7 +1798,7 @@ "connector-type-required": "Se requiere tipo conector.", "connectors": "Configuración de conectores", "create-new-gateway": "Crear un gateway nuevo", - "create-new-gateway-text": "Estás seguro de crear un nuevo gateway con el nombre: '{{gatewayName}}'?", + "create-new-gateway-text": "Crear un nuevo gateway con el nombre: '{{gatewayName}}'?", "delete": "Borrar configuración", "download-tip": "Descargar fichero de configuración", "gateway": "Gateway", @@ -1390,17 +1860,12 @@ "tls-path-private-key": "Ruta a la clave privada en el gateway", "toggle-fullscreen": "Pantalla completa fullscreen", "transformer-json-config": "Configuración JSON*", - "update-config": "Añadir/actualizar configuración JSON", - "state-title": "Estado gateway", - "show-config-tip": "Mostrar configuración gateway", - "title-show-config": "Mostrar configuración gateway", - "read-only": "Solo lectura", - "read-write": "" + "update-config": "Añadir/actualizar configuración JSON" }, "grid": { - "delete-item-title": "¿Estás seguro que quieres eliminar este item?", + "delete-item-title": "¿Quieres eliminar este item?", "delete-item-text": "Atención, tras la confirmación el item será eliminado y la información relacionada será irrecuperable.", - "delete-items-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 item} other {# items} }?", + "delete-items-title": "¿Quieres eliminar { count, plural, 1 {1 item} other {# items} }?", "delete-items-action-title": "Eliminar { count, plural, 1 {1 item} other {# items} }", "delete-items-text": "Atención, tras la confirmación los items seleccionados serán eliminados y la información relacionada será irrecuperable.", "add-item-text": "Agregar nuevo item", @@ -1424,10 +1889,10 @@ "import": { "no-file": "Ningún archivo seleccionado", "drop-file": "Suelte un archivo JSON o haga clic para seleccionar un archivo para cargar.", + "drop-file-csv": "Suelte un archivo CSV o haga clic para seleccionar un archivo para cargar.", "column-value": "Valor", "column-title": "Título", "column-example": "Datos de ejemplo", - "drop-file-csv": "Suelte un archivo CSV o haga clic para seleccionar un archivo para cargar.", "column-key": "Clave de atributo/telemetría", "csv-delimiter": "Delimitador CSV", "csv-first-line-header": "La primera línea contiene nombres de columna.", @@ -1446,6 +1911,7 @@ "entity-field": "Campo de entidad", "access-token": "Token de acceso", "isgateway": "Es Gateway", + "activity-time-from-gateway-device": "Fecha de actividad desde el dispositivo gateway", "description": "Descripción" }, "stepper-text": { @@ -1489,6 +1955,7 @@ "legend": { "direction": "Dirección de la leyenda", "position": "Posición de la leyenda", + "sort-legend": "Ordenar claves en leyenda", "show-max": "Mostrar valor máximo", "show-min": "Mostrar valor mínimo", "show-avg": "Mostrar valor promedio", @@ -1565,20 +2032,21 @@ "any-relation-type": "Cualquier tipo", "add": "Añadir relación", "edit": "Editar relación", - "delete-to-relation-title": "¿Estás seguro que quieres eliminar la relación con la entidad '{{entityName}}'?", + "delete-to-relation-title": "¿Quieres eliminar la relación con la entidad '{{entityName}}'?", "delete-to-relation-text": "Atención, tras la confirmación la entidad '{{entityName}}' no estará relacionada con la entidad actual.", - "delete-to-relations-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", + "delete-to-relations-title": "¿Quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", "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.", - "delete-from-relation-title": "¿Estás seguro que quieres eliminar la relación con la entidad '{{entityName}}'?", + "delete-from-relation-title": "¿Quieres eliminar la relación con la entidad '{{entityName}}'?", "delete-from-relation-text": "Atención, tras la confirmación la entidad actual no estará relacionada con la entidad '{{entityName}}'.", - "delete-from-relations-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", + "delete-from-relations-title": "¿Quieres eliminar { count, plural, 1 {1 relación} other {# relaciones} }?", "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.", "remove-relation-filter": "Quitar filtro de relación", "add-relation-filter": "Añadir filtro de relación", "any-relation": "Cualquier relación", "relation-filters": "Filtro de relación", "additional-info": "Información adicional (JSON)", - "invalid-additional-info": "Error al analizar el fichero JSON de información adicional." + "invalid-additional-info": "Error al analizar el fichero JSON de información adicional.", + "no-relations-text": "No se encontraron relaciones" }, "rulechain": { "rulechain": "Cadena de Regla", @@ -1590,9 +2058,9 @@ "description": "Descripción", "add": "Añadir Cadena", "set-root": "Hacer la cadena de reglas Raíz", - "set-root-rulechain-title": "¿Estás seguro de que desea hacer la cadena de reglas '{{ruleChainName}}' de tipo raíz?", + "set-root-rulechain-title": "¿Desea hacer la cadena de reglas '{{ruleChainName}}' de tipo raíz?", "set-root-rulechain-text": "Tras la confirmación, la cadena de reglas se volverá raíz y manejará todos los mensajes de transporte entrantes.", - "delete-rulechain-title": "¿Estás seguro que quieres eliminar la cadena de reglas '{{ruleChainName}}'?", + "delete-rulechain-title": "¿Quieres eliminar la cadena de reglas '{{ruleChainName}}'?", "delete-rulechain-text": "Atención, tras la confirmación la cadena de reglas y todos los datos serán irrecuperables.", "delete-rulechains-title": "¿Está seguro que quieres eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }?", "delete-rulechains-action-title": "Eliminar { count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} }", @@ -1616,6 +2084,9 @@ "rulechain-required": "Cadena de reglas requerida", "management": "Gestión de reglas", "debug-mode": "Modo Debug", + "search": "Buscar cadenas de reglas", + "selected-rulechains": "{ count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} } seleccionadas", + "open-rulechain": "Abrir cadena de reglas", "assign-rulechains": "Asignar cadenas de reglas", "assign-new-rulechain": "Asignar nueva cadena de reglas", "delete-rulechains": "Eliminar cadenas de reglas", @@ -1694,9 +2165,9 @@ "type-unknown": "Desconocido", "type-unknown-details": "Regla de nodo no resuelta", "directive-is-not-loaded": "La directiva de configuración definida '{{directiveName}}' no está disponible.", - "ui-resources-load-error": "Error al cargar los recursos de configuración ui.", + "ui-resources-load-error": "Error al cargar los recursos de configuración UI.", "invalid-target-rulechain": "No se puede resolver la cadena de reglas objetivo!", - "test-script-function": "Probar Script Función", + "test-script-function": "Probar Script de función", "message": "Mensaje", "message-type": "Tipo de mensaje", "select-message-type": "Seleccionar tipo de mensaje", @@ -1708,11 +2179,16 @@ "help": "Ayuda", "reset-debug-mode": "Restablecer el modo de depuración en todos los nodos" }, + "timezone": { + "timezone": "Zona Horaria", + "select-timezone": "Seleccionar zona horaria", + "no-timezones-matching": "No hay zonas horarias que coincidan con '{{timezone}}'.", + "timezone-required": "Se requiere zona horaria." + }, "queue": { - "select_name": "Selecciona el nombre de la cola", - "name": "Nombre Cola", - "name_required": "Necesario especificar el nombre de cola" - + "select_name": "Selecciona el nombre de la cola", + "name": "Nombre Cola", + "name_required": "Necesario especificar el nombre de cola" }, "tenant": { "tenant": "Propietario", @@ -1725,9 +2201,9 @@ "add-tenant-text": "Agregar nuevo propietario", "no-tenants-text": "Ningún propietario encontrado", "tenant-details": "Detalles del propietario", - "delete-tenant-title": "¿Estás seguro que quieres eliminar el propietario '{{tenantTitle}}'?", + "delete-tenant-title": "¿Quieres eliminar el propietario '{{tenantTitle}}'?", "delete-tenant-text": "Atención, tras la confirmación el propietario será eliminado y la información relacionada será irrecuperable.", - "delete-tenants-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 propietario} other {# propietarios} }?", + "delete-tenants-title": "¿Quieres eliminar { count, plural, 1 {1 propietario} other {# propietarios} }?", "delete-tenants-action-title": "Eliminar { count, plural, 1 {1 propietario} other {# propietarios} }", "delete-tenants-text": "Atención, tras la confirmación los propietarios seleccionados serán eliminados y la información relacionada será irrecuperable.", "title": "Título", @@ -1740,21 +2216,110 @@ "select-tenant": "Seleccionar propietario", "no-tenants-matching": "No hay propietarios que coincidan con '{{entity}}' .", "tenant-required": "Propietario requerido", + "search": "Buscar propietarios", + "selected-tenants": "{ count, plural, 1 {1 propietario} other {# propietarios} } seleccionados", "isolated-tb-core": "Procesando en contenedor aislado", "isolated-tb-rule-engine": "Procesando en contenedor Motor de Reglas aislado", "isolated-tb-core-details": "Requiere microservicios separados por propietario aislado", "isolated-tb-rule-engine-details": "Requiere microservicios separados por propietario aislado" }, + "tenant-profile": { + "tenant-profile": "Perfil de propietario", + "tenant-profiles": "Perfiles de propietarios", + "add": "Añadir perfil de propietario", + "edit": "Editar perfil de propietario", + "tenant-profile-details": "Detalles perfil de propietario", + "no-tenant-profiles-text": "No se encontraron perfiles de propietario", + "search": "Buscar perfiles de propietario", + "selected-tenant-profiles": "{ count, plural, 1 {1 perfil de propietario} other {# perfiles de propietario} } seleccionados", + "no-tenant-profiles-matching": "No se han encontrado perfiles de propietario que coincidan con '{{entity}}'.", + "tenant-profile-required": "Se requiere perfil de propietario", + "idCopiedMessage": "El ID de perfil de propietario se ha copiado al portapapeles", + "set-default": "Hacer perfil propietario por defecto", + "delete": "Borrar perfil", + "copyId": "Copiar ID de perfil", + "name": "Nombre", + "name-required": "Se requiere nombre.", + "data": "Datos de perfil", + "profile-configuration": "Configuración de perfil", + "description": "Descripción", + "default": "Defecto", + "delete-tenant-profile-title": "Eliminar el perfil propietario '{{tenantProfileName}}'?", + "delete-tenant-profile-text": "Atención, tras la confirmación, el perfil de propietario será borrado y su información relacionada será irrecuperable.", + "delete-tenant-profiles-title": "Eliminar { count, plural, 1 {1 perfil propietario} other {# perfiles propietarios} }?", + "delete-tenant-profiles-text": "Atención, tras la confirmación, los perfiles seleccionados se eliminarán y su información relacionada será irrecuperable.", + "set-default-tenant-profile-title": "Quieres hacer el perfil propietario '{{tenantProfileName}}' por defecto?", + "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.", + "no-tenant-profiles-found": "No se encontraron perfiles de propietario.", + "create-new-tenant-profile": "Crear un nuevo perfil!", + "maximum-devices": "Nº Máximo de dispositivos (0 - sin límite)", + "maximum-devices-required": "Nº Máximo de dispositivos requerido.", + "maximum-devices-range": "Nº Máximo de dispositivos no puede ser negativo", + "maximum-assets": "Nº Máximo de activos (0 - sin límite)", + "maximum-assets-required": "Nº Máximo de activos requerido.", + "maximum-assets-range": "Nº Máximo de activos no puede ser negativo", + "maximum-customers": "Nº Máximo de clientes (0 - sin límite)", + "maximum-customers-required": "Nº Máximo de clientes requerido.", + "maximum-customers-range": "Nº Máximo de clientes no puede ser negativo", + "maximum-users": "Nº Máximo de usuarios (0 - sin límite)", + "maximum-users-required": "Nº Máximo de usuarios requerido.", + "maximum-users-range": "Nº Máximo de usuarios no puede ser negativo", + "maximum-dashboards": "Nº Máximo de paneles (0 - sin límite)", + "maximum-dashboards-required": "Nº Máximo de paneles requerido.", + "maximum-dashboards-range": "Nº Máximo de paneles no puede ser negativo", + "maximum-rule-chains": "Nº Máximo de cadenas de reglas (0 - sin límite)", + "maximum-rule-chains-required": "Nº Máximo de cadenas de reglas requerido.", + "maximum-rule-chains-range": "Nº Máximo de cadenas de reglas no puede ser negativo", + "transport-tenant-msg-rate-limit": "Tasa de mensajes de transporte por propietario.", + "transport-tenant-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría por propietario.", + "transport-tenant-telemetry-data-points-rate-limit": "Tasa de datapoints por propietario.", + "transport-device-msg-rate-limit": "Tasa de mensajes de dispositivo.", + "transport-device-telemetry-msg-rate-limit": "Tasa de mensajes de telemetría de dispositivo.", + "transport-device-telemetry-data-points-rate-limit": "Tasa de datapoints de telemetría de dispositivo.", + "max-transport-messages": "Nº Máximo de mensajes de transporte (0 - sin límite)", + "max-transport-messages-required": "Nº Máximo de mensajes de transporte requerido.", + "max-transport-messages-range": "Nº Máximo de mensajes de transporte no puede ser negativo", + "max-transport-data-points": "Nº Máximo de datapoints transporte (0 - sin límite)", + "max-transport-data-points-required": "Nº Máximo de datapoints transporte requerido.", + "max-transport-data-points-range": "Nº Máximo de datapoints transporte no puede ser negativo", + "max-r-e-executions": "Nº Máximo de ejecuciones de motor de reglas (0 - sin límite)", + "max-r-e-executions-required": "Nº Máximo de ejecuciones de motor de reglas requerido.", + "max-r-e-executions-range": "Nº Máximo de ejecuciones de motor de reglas no puede ser negativo", + "max-j-s-executions": "Nº Máximo de ejecuciones JavaScript (0 - sin límite)", + "max-j-s-executions-required": "Nº Máximo de ejecuciones JavaScript requerido.", + "max-j-s-executions-range": "Nº Máximo de ejecuciones JavaScript no puede ser negativo", + "max-d-p-storage-days": "Nº Máximo de días a grabar en datapoints (0 - sin límite)", + "max-d-p-storage-days-required": "Nº Máximo de días requerido.", + "max-d-p-storage-days-range": "Nº Máximo de días no puede ser negativo", + "default-storage-ttl-days": "Días por defecto grabado TTL (0 - sin límite)", + "default-storage-ttl-days-required": "Días por defecto TTL requerido.", + "default-storage-ttl-days-range": "Días por defecto TTL no puede ser negativo", + "max-rule-node-executions-per-message": "Nº Máximo de ejecuciones (cadena de reglas) por mensaje (0 - sin límite)", + "max-rule-node-executions-per-message-required": "Nº Máximo de ejecuciones por mensaje requerido.", + "max-rule-node-executions-per-message-range": "Nº Máximo de ejecuciones por mensaje no puede ser negativo", + "max-emails": "Nº Máximo de emails (0 - sin límite)", + "max-emails-required": "Nº Máximo de emails requerido.", + "max-emails-range": "Nº Máximo de emails no puede ser negativo", + "max-sms": "Nº Máximo de mensajes SMS (0 - sin límite)", + "max-sms-required": "Nº Máximo de mensajes SMS requerido.", + "max-sms-range": "Nº Máximo de mensajes SMS no puede ser negativo" + }, "timeinterval": { - "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }", - "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }", - "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }", - "days-interval": "{ days, plural, 1 {1 día} other {# días} }", - "days": "Días", - "hours": "Horas", - "minutes": "Minutos", + "seconds-interval": "{ seconds, plural, 1 {1 segundo} other {# segundos} }", + "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minutos} }", + "hours-interval": "{ hours, plural, 1 {1 hora} other {# horas} }", + "days-interval": "{ days, plural, 1 {1 día} other {# días} }", + "days": "Días", + "hours": "Horas", + "minutes": "Minutos", + "seconds": "Segundos", + "advanced": "Avanzado" + }, + "timeunit": { "seconds": "Segundos", - "advanced": "Avanzado" + "minutes": "Minutos", + "hours": "Horas", + "days": "Días" }, "timewindow": { "days": "{ days, plural, 1 { día } other {# días } }", @@ -1785,9 +2350,9 @@ "add-user-text": "Agregar nuevo usuario", "no-users-text": "Ningún usuario encontrado", "user-details": "Detalles del usuario", - "delete-user-title": "¿Estás seguro que quieres eliminar el usuario '{{userEmail}}'?", + "delete-user-title": "¿Eliminar el usuario '{{userEmail}}'?", "delete-user-text": "Atención, tras la confirmación el usuario seleccionado será eliminado y la información relacionada será irrecuperable.", - "delete-users-title": "¿Estás seguro que quieres eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?", + "delete-users-title": "¿Eliminar { count, plural, 1 {1 usuario} other {# usuarios} }?", "delete-users-action-title": "Borrar { count, plural, 1 {1 usuario} other {# usuarios} }", "delete-users-text": "Atención, tras la confirmación los usuarios seleccionados serán eliminados y la información relacionada será irrecuperable.", "activation-email-sent-message": "Mail de activación enviado con éxito!", @@ -1813,6 +2378,8 @@ "details": "Detalles", "login-as-tenant-admin": "Iniciar sesión como Administrador Propietario", "login-as-customer-user": "Iniciar sesión como Usuario Cliente", + "search": "Buscar usuarios", + "selected-users": "{ count, plural, 1 {1 usuario} other {# usuarios} } seleccionados", "disable-account": "Deshabilitar cuenta de usuario", "enable-account": "Habilitar cuenta de usuario", "enable-account-message": "¡La cuenta de usuario se ha habilitado correctamente!", @@ -1822,18 +2389,23 @@ "type": "Tipo de valor", "string": "Cadena de texto", "string-value": "Valor de cadena de texto", + "string-value-required": "Se requiere valor de cadena de texto", "integer": "Nro entero", "integer-value": "Valor de nro entero", - "invalid-integer-value": "Valor inválido", + "integer-value-required": "Se requiere valor entero", + "invalid-integer-value": "Valor de entero inválido", "double": "Nro decimal", "double-value": "Valor nro decimal", + "double-value-required": "Se requiere valor nro decimal", "boolean": "Booleano", "boolean-value": "Valor booleano", "false": "Falso", "true": "Verdadero", "long": "Nro Largo", "json": "JSON", - "json-value": "Valor JSON" + "json-value": "Valor JSON", + "json-value-invalid": "El valor JSON tiene un formato inválido", + "json-value-required": "Se requiere valor JSON" }, "widget": { "widget-library": "Bibloteca de Widgets", @@ -1845,7 +2417,7 @@ "widget-type-load-error": "El widget no pudo ser cargado debido a estos errores:", "remove": "Eliminar widget", "edit": "Editar widget", - "remove-widget-title": "¿Estás seguro que quieres eliminar el widget '{{widgetTitle}}'?", + "remove-widget-title": "¿Eliminar el widget '{{widgetTitle}}'?", "remove-widget-text": "Atención, tras la confirmación el widget será eliminado y toda la información relacionada será irrecuperable..", "timeseries": "Series de tiempo", "search-data": "Buscar datos", @@ -1869,6 +2441,7 @@ "type": "Tipo", "resources": "Recursos", "resource-url": "URL JavaScript/CSS", + "resource-is-module": "Es módulo", "remove-resource": "Eliminar recurso", "add-resource": "Agregar recurso", "html": "HTML", @@ -1878,7 +2451,7 @@ "datakey-settings-schema": "Esquema de configuración de clave de datos", "javascript": "Javascript", "js": "JS", - "remove-widget-type-title": "¿Estás seguro que quieres eliminar el tipo del widget '{{widgetName}}'?", + "remove-widget-type-title": "¿Eliminar el tipo del widget '{{widgetName}}'?", "remove-widget-type-text": "Atención, tras la confirmación el tipo será eliminado y la información relacionada será irrecuperable.", "remove-widget-type": "Eliminar tipo de widget.", "add-widget-type": "Agregar nuevo tipo de widget", @@ -1886,7 +2459,10 @@ "widget-template-load-failed-error": "Error al cargar la plantilla del widget!", "add": "Agregar Widget", "undo": "Deshacer cambios", - "export": "Exportar widget" + "export": "Exportar widget", + "no-data": "No hay datos para mostrar en widget", + "data-overflow": "El widget muestra {{count}} de {{total}} entidades", + "alarm-data-overflow": "El widget muestra alarmas para {{allowedEntities}} entidades (máximo permitido) de {{totalEntities}} entidades" }, "widget-action": { "header-button": "Botón de encabezado widget", @@ -1899,7 +2475,14 @@ "target-dashboard-state-required": "Se requiere estado de panel de destino", "set-entity-from-widget": "Establecer entidad desde widget", "target-dashboard": "Panel de destino", - "open-right-layout": "Abrir diseño de panel (derecho)(vista móvil)" + "open-right-layout": "Abrir diseño de panel (derecho)(vista móvil)", + "open-in-separate-dialog": "Abrir en un diálogo separado", + "dialog-title": "Título del diálogo", + "dialog-hide-dashboard-toolbar": "Ocultar barra de herramientas en el diálogo", + "dialog-width": "Ancho de diálogo en porcentaje relativo al ancho del viewport", + "dialog-height": "Alto de diálogo en porcentaje relativo al alto del viewport", + "dialog-size-range-error": "El tamaño del diálogo debe ser entre un rango de 1 a 100", + "open-new-browser-tab": "Abrir en una nueva pestaña" }, "widgets-bundle": { "current": "Paquete actual", @@ -1913,9 +2496,9 @@ "empty": "Paquete de widgets vacío.", "details": "Detalles", "widgets-bundle-details": "Detalles del paquete de Widgets", - "delete-widgets-bundle-title": "¿Estás seguro que quieres eliminar el paquete de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-title": "¿Eliminar el paquete de widgets '{{widgetsBundleTitle}}'?", "delete-widgets-bundle-text": "Atención, tras la confirmación todos los paquetes seleccionados serán eliminados y su información relacionada será irrecuperable.", - "delete-widgets-bundles-title": "¿Estás seguro que deseas eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?", + "delete-widgets-bundles-title": "¿Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }?", "delete-widgets-bundles-action-title": "Eliminar { count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} }", "delete-widgets-bundles-text": "Atención, tras la confirmación todos los paquetes seleccionados serán eliminados y la información relacionada será irrecuperable.", "no-widgets-bundles-matching": "Ningún paquete '{{widgetsBundle}}' encontrado.", @@ -1926,7 +2509,10 @@ "export-failed-error": "Imposible exportar paquete de widgets: {{error}}", "create-new-widgets-bundle": "Crear nuevo paquete de widgets", "widgets-bundle-file": "Archivo de paquete de widgets", - "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida." + "invalid-widgets-bundle-file-error": "Imposible importar paquete de widgets: Estructura de datos inválida.", + "search": "Buscar paquete de widgets", + "selected-widgets-bundles": "{ count, plural, 1 {1 paquete de widgets} other {# paquetes de widgets} } seleccionados", + "open-widgets-bundle": "Abrir paquete de widgets" }, "widget-config": { "data": "Datos", @@ -1965,6 +2551,7 @@ "action": "Acción", "add-action": "Añadir acción", "search-actions": "Buscar acciones", + "no-actions-text": "No se encontraron actiones", "action-source": "Origen de acción", "action-source-required": "Origen de acción requerido.", "action-name": "Nombre", @@ -1976,7 +2563,7 @@ "edit-action": "Editar acción", "delete-action": "Borrar acción", "delete-action-title": "Borrar acción de widget", - "delete-action-text": "Estás seguro de borrar la acción de widget con el nombre '{{actionName}}'?", + "delete-action-text": "Eliminar la acción de widget con el nombre '{{actionName}}'?", "display-icon": "Mostrar icono del título", "icon-color": "Color del icono", "icon-size": "Tamaño del icono" @@ -2044,7 +2631,7 @@ "Custom interval": "Intervalo personalizado", "Interval": "Intervalo", "Step size": "Número de pasos", - "Ok": "De acuerdo" + "Ok": "Ok" } }, "input-widgets": { @@ -2062,8 +2649,11 @@ "entity-coordinate-required": "Se requieren ambos campos (latitud y longitud)", "entity-timeseries-required": "Se requiere la serie de tiempo de la entidad", "get-location": "Obtener localización actual", + "invalid-date": "Fecha inválida", "latitude": "Latitud", "longitude": "Longitud", + "min-value-error": "El valor mínimo es {{value}}", + "max-value-error": "El valor máximo es {{value}}", "not-allowed-entity": "La entidad seleccionada no puede tener atributos compartidos", "no-attribute-selected": "No se seleccionó ningún atributo", "no-datakey-selected": "No se seleccionó ninguna clave de datos", @@ -2072,6 +2662,9 @@ "no-image": "Sin imagen", "no-support-geolocation": "Tu navegador no soporta geolocalización", "no-support-web-camera": "No hay cámara web compatible", + "enable-https-use-widget": "Por favor, activa HTTPS para poder usar este widget", + "no-found-your-camera": "No es posible encontrar la cámara", + "no-permission-camera": "Permiso denegado por el usuario / Esta página no tiene permisos para usar la cámara", "no-timeseries-selected": "No hay series de tiempo seleccionadas", "secret-key": "Clave", "secret-key-required": "Clave requerida", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 670aa58fc5..c33f591272 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -1850,9 +1850,7 @@ "name-required": "Необхідно задати ім'я", "configuration": "Конфігурація", "schedule": "Розклад", - "start": "Початок", - "date": "Дата", - "time": "Час", + "start-time": "Початок", "repeat": "Повтор", "repeats": "Повтори", "daily": "Щодня", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index dcdc0909c0..b993a8dbd4 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -1118,6 +1118,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.12.5": + version "7.12.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.5.tgz#410e7e487441e1b360c29be715d870d9b985882e" + integrity sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@7.10.4", "@babel/template@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -7941,7 +7948,7 @@ rc-util@^4.15.3: react-lifecycles-compat "^3.0.4" shallowequal "^1.1.0" -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== @@ -7949,6 +7956,15 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: react-is "^16.12.0" shallowequal "^1.1.0" +rc-util@^5.0.6: + version "5.7.0" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.7.0.tgz#776b14cf5bbfc24f419fd40c42ffadddda0718fc" + integrity sha512-0hh5XkJ+vBDeMJsHElqT1ijMx+gC3gpClwQ10h/5hccrrgrMx8VUem183KLlH1YrWCfMMPmDXWWNnwsn+p6URw== + dependencies: + "@babel/runtime" "^7.12.5" + react-is "^16.12.0" + shallowequal "^1.1.0" + rc-virtual-list@^1.1.2: version "1.1.6" resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.6.tgz#b255baf9aacde149a8893324e6307214094f4c0a" diff --git a/ui/src/app/edge/downlinks/downlink.scss b/ui/src/app/edge/downlinks/downlink.scss deleted file mode 100644 index 9f975fc1e1..0000000000 --- a/ui/src/app/edge/downlinks/downlink.scss +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -md-list.tb-edge-downlinks-table { - padding: 0; - - md-list-item { - padding: 0; - } - - .tb-row { - height: 48px; - padding: 0; - overflow: hidden; - - .tb-cell { - text-overflow: ellipsis; - - &.tb-scroll { - overflow-x: auto; - overflow-y: hidden; - white-space: nowrap; - } - - &.tb-nowrap { - white-space: nowrap; - } - } - } - - .tb-row:hover { - background-color: #eee; - } - - .tb-header:hover { - background: none; - } - - .tb-header { - .tb-cell { - font-size: 12px; - font-weight: 700; - color: rgba(0, 0, 0, .54); - white-space: nowrap; - background: none; - } - } - - .tb-cell { - &:first-child { - padding-left: 14px; - } - - &:last-child { - padding-right: 14px; - } - padding: 0 6px; - margin: auto 0; - overflow: hidden; - font-size: 13px; - color: rgba(0, 0, 0, .87); - text-align: left; - vertical-align: middle; - - .md-button { - padding: 0; - margin: 0; - } - } - - .tb-cell.tb-number { - text-align: right; - } -} - -#tb-edge-downlinks-content { - width: 100%; - min-width: 400px; - height: 100%; - min-height: 50px; -} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js b/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js deleted file mode 100644 index 432bdf4d50..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.controller.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import $ from 'jquery'; -import 'brace/ext/language_tools'; -import 'brace/ext/searchbox'; -import 'brace/mode/java'; -import 'brace/theme/github'; -import beautify from 'js-beautify'; - -/* eslint-disable angular/angularelement */ - -const js_beautify = beautify.js; - -/*@ngInject*/ -export default function EdgeDownlinksContentDialogController($mdDialog, types, content, contentType, title, showingCallback) { - - var vm = this; - - showingCallback.onShowing = function(scope, element) { - updateEditorSize(element); - } - - vm.content = content; - vm.title = title; - - var mode; - if (contentType) { - mode = types.contentType[contentType].code; - if (contentType == types.contentType.JSON.value && vm.content) { - vm.content = js_beautify(vm.content, {indent_size: 4}); - } - } else { - mode = 'java'; - } - - vm.contentOptions = { - useWrapMode: false, - mode: mode, - showGutter: false, - showPrintMargin: false, - theme: 'github', - advanced: { - enableSnippets: false, - enableBasicAutocompletion: false, - enableLiveAutocompletion: false - }, - onLoad: function (_ace) { - vm.editor = _ace; - } - }; - - function updateEditorSize(element) { - var newHeight = 400; - var newWidth = 600; - if (vm.content && vm.content.length > 0) { - var lines = vm.content.split('\n'); - newHeight = 16 * lines.length + 16; - var maxLineLength = 0; - for (var i = 0; i < lines.length; i++) { - var line = lines[i].replace(/\t/g, ' ').replace(/\n/g, ''); - var lineLength = line.length; - maxLineLength = Math.max(maxLineLength, lineLength); - } - newWidth = 8 * maxLineLength + 16; - } - $('#tb-edge-downlinks-content', element).height(newHeight.toString() + "px") - .width(newWidth.toString() + "px"); - vm.editor.resize(); - } - - vm.close = close; - - function close () { - $mdDialog.hide(); - } - -} - -/* eslint-enable angular/angularelement */ diff --git a/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.tpl.html b/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.tpl.html deleted file mode 100644 index 798990ec07..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-content-dialog.tpl.html +++ /dev/null @@ -1,42 +0,0 @@ - - - -
-

{{ vm.title }}

- - - - -
-
- -
- -
-
- - - {{ 'action.close' | - translate }} - - -
diff --git a/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js deleted file mode 100644 index 3af243ca48..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-header.directive.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-disable import/no-unresolved, import/default */ - -import edgeDownlinksHeaderTemplate from './edge-downlinks-header.tpl.html' - -/* eslint-enable import/no-unresolved, import/default */ - -/*@ngInject*/ -export default function EdgeDownlinksHeaderDirective($compile, $templateCache) { - - var linker = function (scope, element) { - - var template = edgeDownlinksHeaderTemplate; - - element.html($templateCache.get(template)); - $compile(element.contents())(scope); - } - - return { - restrict: "A", - replace: false, - link: linker, - scope: false - }; -} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js deleted file mode 100644 index 16015d12db..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-row.directive.js +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* eslint-disable import/no-unresolved, import/default */ - -import edgeDownlinksContentTemplate from './edge-downlinks-content-dialog.tpl.html'; -import edgeDownlinlsRowTemplate from './edge-downlinks-row.tpl.html'; - -/* eslint-enable import/no-unresolved, import/default */ - -/*@ngInject*/ -export default function EdgeDownlinksRowDirective($compile, $templateCache, $mdDialog, $document, $translate, - types, utils, toast, entityService, ruleChainService) { - - var linker = function (scope, element, attrs) { - - var template = edgeDownlinlsRowTemplate; - - element.html($templateCache.get(template)); - $compile(element.contents())(scope); - - scope.types = types; - scope.downlink = attrs.downlink; - - scope.showEdgeEntityContent = function($event, title, contentType) { - var onShowingCallback = { - onShowing: function(){} - } - if (!contentType) { - contentType = null; - } - var content = ''; - switch(scope.downlink.type) { - case types.edgeEventType.relation: - content = angular.toJson(scope.downlink.body); - showDialog(); - break; - case types.edgeEventType.ruleChainMetaData: - content = ruleChainService.getRuleChainMetaData(scope.downlink.entityId, {ignoreErrors: true}).then( - function success(info) { - showDialog(); - return angular.toJson(info); - }, function fail() { - showError(); - }); - break; - default: - content = entityService.getEntity(scope.downlink.type, scope.downlink.entityId, {ignoreErrors: true}).then( - function success(info) { - showDialog(); - return angular.toJson(info); - }, function fail() { - showError(); - }); - break; - } - function showDialog() { - $mdDialog.show({ - controller: 'EdgeDownlinksContentDialogController', - controllerAs: 'vm', - templateUrl: edgeDownlinksContentTemplate, - locals: {content: content, title: title, contentType: contentType, showingCallback: onShowingCallback}, - parent: angular.element($document[0].body), - fullscreen: true, - targetEvent: $event, - multiple: true, - onShowing: function(scope, element) { - onShowingCallback.onShowing(scope, element); - } - }); - } - function showError() { - toast.showError($translate.instant('edge.load-entity-error')); - } - } - - scope.checkEdgeDownlinksType = function (type) { - return !(type === types.edgeEventType.widgetType || - type === types.edgeEventType.adminSettings || - type === types.edgeEventType.widgetsBundle ); - } - - scope.checkTooltip = function($event) { - var el = $event.target; - var $el = angular.element(el); - if(el.offsetWidth < el.scrollWidth && !$el.attr('title')){ - $el.attr('title', $el.text()); - } - } - - $compile(element.contents())(scope); - - scope.updateStatus = function(downlinkCreatedTime) { - var status; - if (downlinkCreatedTime < scope.queueStartTs) { - status = $translate.instant(types.edgeEventStatus.DEPLOYED.name); - scope.statusColor = types.edgeEventStatus.DEPLOYED.color; - } else { - status = $translate.instant(types.edgeEventStatus.PENDING.name); - scope.statusColor = types.edgeEventStatus.PENDING.color; - } - return status; - } - } - - return { - restrict: "A", - replace: false, - link: linker, - scope: false - }; -} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js b/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js deleted file mode 100644 index 98bd63abb0..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-table.directive.js +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import './downlink.scss'; - -/* eslint-disable import/no-unresolved, import/default */ - -import edgeDownlinksTableTemplate from './edge-downlinks-table.tpl.html'; - -/* eslint-enable import/no-unresolved, import/default */ - -/*@ngInject*/ -export default function EdgeDownlinksDirective($compile, $templateCache, $rootScope, $translate, types, edgeService, attributeService) { - - var linker = function (scope, element) { - - var template = $templateCache.get(edgeDownlinksTableTemplate); - element.html(template); - - var pageSize = 20; - var startTime = 0; - var endTime = 0; - - scope.timewindow = { - history: { - timewindowMs: 24 * 60 * 60 * 1000 // 1 day - } - } - - scope.topIndex = 0; - - scope.theDownlinks = { - getItemAtIndex: function (index) { - if (index > scope.downlinks.data.length) { - scope.theDownlinks.fetchMoreItems_(index); - return null; - } - var item = scope.downlinks.data[index]; - if (item) { - item.indexNumber = index + 1; - } - return item; - }, - - getLength: function () { - if (scope.downlinks.hasNext) { - return scope.downlinks.data.length + scope.downlinks.nextPageLink.limit; - } else { - return scope.downlinks.data.length; - } - }, - - fetchMoreItems_: function () { - if (scope.downlinks.hasNext && !scope.downlinks.pending) { - if (scope.entityType && scope.entityId && scope.tenantId) { - scope.loadEdgeInfo(); - scope.downlinks.pending = true; - edgeService.getEdgeEvents(scope.entityId, scope.downlinks.nextPageLink).then( - function success(downlinks) { - scope.downlinks.data = scope.downlinks.data.concat(prepareEdgeDownlinksData(downlinks.data)); - scope.downlinks.nextPageLink = downlinks.nextPageLink; - scope.downlinks.hasNext = downlinks.hasNext; - if (scope.downlinks.hasNext) { - scope.downlinks.nextPageLink.limit = pageSize; - } - scope.downlinks.pending = false; - }, - function fail() { - scope.downlinks.hasNext = false; - scope.downlinks.pending = false; - }); - } else { - scope.downlinks.hasNext = false; - } - } - } - }; - - scope.$watch("entityId", function(newVal, prevVal) { - if (newVal && !angular.equals(newVal, prevVal)) { - scope.resetFilter(); - scope.reload(); - } - }); - - scope.$watch("timewindow", function(newVal, prevVal) { - if (newVal && !angular.equals(newVal, prevVal)) { - scope.reload(); - } - }, true); - - scope.resetFilter = function() { - scope.timewindow = { - history: { - timewindowMs: 24 * 60 * 60 * 1000 // 1 day - } - }; - } - - scope.updateTimeWindowRange = function() { - if (scope.timewindow.history.timewindowMs) { - var currentTime = (new Date).getTime(); - startTime = currentTime - scope.timewindow.history.timewindowMs; - endTime = currentTime; - } else { - startTime = scope.timewindow.history.fixedTimewindow.startTimeMs; - endTime = scope.timewindow.history.fixedTimewindow.endTimeMs; - } - } - - scope.reload = function() { - scope.topIndex = 0; - scope.selected = []; - scope.updateTimeWindowRange(); - scope.downlinks = { - data: [], - nextPageLink: { - limit: pageSize, - startTime: startTime, - endTime: endTime - }, - hasNext: true, - pending: false - }; - scope.theDownlinks.getItemAtIndex(pageSize); - } - - scope.noData = function() { - return scope.downlinks.data.length == 0 && !scope.downlinks.hasNext; - } - - scope.hasData = function() { - return scope.downlinks.data.length > 0; - } - - scope.loading = function() { - return $rootScope.loading; - } - - scope.hasScroll = function() { - var repeatContainer = scope.repeatContainer[0]; - if (repeatContainer) { - var scrollElement = repeatContainer.children[0]; - if (scrollElement) { - return scrollElement.scrollHeight > scrollElement.clientHeight; - } - } - return false; - } - - scope.subscriptionId = null; - - scope.loadEdgeInfo = function() { - attributeService.getEntityAttributesValues( - scope.entityType, - scope.entityId, - types.attributesScope.server.value, - types.edgeAttributeKeys.queueStartTs, - null).then( - function success(attributes) { - attributes.length > 0 ? scope.onEdgeAttributesUpdate(attributes) : scope.queueStartTs = 0; - }); - scope.checkSubscription(); - } - - scope.onEdgeAttributesUpdate = function(attributes) { - let edgeAttributes = attributes.reduce(function (map, attribute) { - map[attribute.key] = attribute; - return map; - }, {}); - if (edgeAttributes.queueStartTs) { - scope.queueStartTs = edgeAttributes.queueStartTs.lastUpdateTs; - } - } - - scope.checkSubscription = function() { - var newSubscriptionId = null; - if (scope.entityId && scope.entityType && types.attributesScope.server.value) { - newSubscriptionId = - attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, types.attributesScope.server.value); - } - if (scope.subscriptionId && scope.subscriptionId != newSubscriptionId) { - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); - } - scope.subscriptionId = newSubscriptionId; - } - - scope.$on('$destroy', function () { - if (scope.subscriptionId) { - attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); - } - }); - - scope.reload(); - - $compile(element.contents())(scope); - } - function prepareEdgeDownlinksData(data) { - - data.forEach( - edgeDownlink => { - edgeDownlink.edgeEventActionText = $translate.instant(types.edgeEventActionTypeTranslations[edgeDownlink.action].name); - edgeDownlink.edgeEventTypeText = $translate.instant(types.edgeEventTypeTranslations[edgeDownlink.type].name); - } - ); - return data; - } - - return { - restrict: "E", - link: linker, - scope: { - entityType: '=', - entityId: '=', - tenantId: '=' - } - }; -} diff --git a/ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html b/ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html deleted file mode 100644 index 0e66b2b685..0000000000 --- a/ui/src/app/edge/downlinks/edge-downlinks-table.tpl.html +++ /dev/null @@ -1,46 +0,0 @@ - - -
- - - refresh - - {{ 'action.refresh' | translate }} - - -
- - - - - - edge.no-downlinks-prompt - - - - - - - - -