Commit 47e177c2c2e7b63b682008e77a0c962954026bd4
Committed by
GitHub
Merge branch 'master' into feacher/add_map_here
Showing
100 changed files
with
3567 additions
and
189 deletions
Too many changes to show.
To preserve performance only 100 of 164 files are displayed.
@@ -116,6 +116,22 @@ | @@ -116,6 +116,22 @@ | ||
116 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | 116 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", |
117 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" | 117 | "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" |
118 | } | 118 | } |
119 | + }, | ||
120 | + { | ||
121 | + "alias": "entities_hierarchy", | ||
122 | + "name": "Entities hierarchy", | ||
123 | + "descriptor": { | ||
124 | + "type": "latest", | ||
125 | + "sizeX": 7.5, | ||
126 | + "sizeY": 3.5, | ||
127 | + "resources": [], | ||
128 | + "templateHtml": "<tb-entities-hierarchy-widget \n hierarchy-id=\"hierarchyId\"\n ctx=\"ctx\">\n</tb-entities-hierarchy-widget>", | ||
129 | + "templateCss": "", | ||
130 | + "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.hierarchyId = \"hierarchy-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('entities-hierarchy-data-updated', self.ctx.$scope.hierarchyId);\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", | ||
131 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | ||
132 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", | ||
133 | + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Entities hierarchy\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"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;\"}]}],\"widgetStyle\":{},\"actions\":{}}" | ||
134 | + } | ||
119 | } | 135 | } |
120 | ] | 136 | ] |
121 | } | 137 | } |
1 | +{ | ||
2 | + "widgetsBundle": { | ||
3 | + "alias": "date", | ||
4 | + "title": "Date", | ||
5 | + "image": null | ||
6 | + }, | ||
7 | + "widgetTypes": [ | ||
8 | + { | ||
9 | + "alias": "date_range_navigator", | ||
10 | + "name": "Date-range-navigator", | ||
11 | + "descriptor": { | ||
12 | + "type": "static", | ||
13 | + "sizeX": 5, | ||
14 | + "sizeY": 5.5, | ||
15 | + "resources": [], | ||
16 | + "templateHtml": "<date-range-navigator-widget class=\"date-range-navigator-widget\" ctx=\"ctx\"></date-range-navigator-widget>", | ||
17 | + "templateCss": "", | ||
18 | + "controllerScript": "self.onInit = function() {\n scope = self.ctx.$scope;\n scope.ctx = self.ctx;\n}", | ||
19 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"hidePicker\": {\n \"title\": \"Hide date range picker\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"onePanel\": {\n \"title\": \"Date range picker one panel\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"autoConfirm\": {\n \"title\": \"Date range picker auto confirm\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showTemplate\": {\n \"title\": \"Date range picker show template\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"firstDayOfWeek\": {\n \"title\": \"First day of the week\",\n \"type\": \"number\",\n \"default\": 1\n },\n \"hideInterval\": {\n \"title\": \"Hide interval\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"initialInterval\": {\n\t\t\t\t\"title\": \"Initial interval\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"week\"\n\t\t\t},\n \"hideStepSize\": {\n \"title\": \"Hide step size\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"stepSize\": {\n\t\t\t\t\"title\": \"Initial step size\",\n\t\t\t\t\"type\": \"string\",\n\t\t\t\t\"default\": \"day\"\n\t\t\t},\n \"hideLabels\": {\n \"title\": \"Hide labels\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useSessionStorage\": {\n \"title\": \"Use session storage\",\n \"type\": \"boolean\",\n \"default\": true\n }\n }\n },\n \"form\": [\n \"hidePicker\",\n\t\t\"onePanel\",\n\t\t\"autoConfirm\",\n\t\t\"showTemplate\",\n\t\t\"firstDayOfWeek\",\n \"hideInterval\",\n {\n\t\t\t\"key\": \"initialInterval\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n \"hideStepSize\",\n {\n\t\t\t\"key\": \"stepSize\",\n\t\t\t\"type\": \"rc-select\",\n\t\t\t\"multiple\": false,\n\t\t\t\"items\": [\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"hour\",\n\t\t\t\t\t\"label\": \"Hour\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"day\",\n\t\t\t\t\t\"label\": \"Day\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"week\",\n\t\t\t\t\t\"label\": \"Week\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"twoWeeks\",\n\t\t\t\t\t\"label\": \"2 weeks\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"month\",\n\t\t\t\t\t\"label\": \"Month\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"threeMonths\",\n\t\t\t\t\t\"label\": \"3 months\"\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\t\"value\": \"sixMonths\",\n\t\t\t\t\t\"label\": \"6 months\"\n\t\t\t\t}\n\t\t\t]\n\t\t},\n\t\t\"hideLabels\",\n\t\t\"useSessionStorage\"\n ]\n}", | ||
20 | + "dataKeySettingsSchema": "{}\n", | ||
21 | + "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" | ||
22 | + } | ||
23 | + } | ||
24 | + ] | ||
25 | +} |
@@ -118,6 +118,22 @@ | @@ -118,6 +118,22 @@ | ||
118 | } | 118 | } |
119 | }, | 119 | }, |
120 | { | 120 | { |
121 | + "alias": "test", | ||
122 | + "name": "Trip Animation", | ||
123 | + "descriptor": { | ||
124 | + "type": "timeseries", | ||
125 | + "sizeX": 8.5, | ||
126 | + "sizeY": 6.5, | ||
127 | + "resources": [], | ||
128 | + "templateHtml": "<trip-animation self=\"self\" ctx=\"self.ctx\" ></trip-animation>", | ||
129 | + "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", | ||
130 | + "controllerScript": " self.onInit = function() {\n var $scope = self.ctx.$scope;\n $scope.self = self;\n }\n \n \n self.actionSources = function () {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n };\n", | ||
131 | + "settingsSchema": "{\r\n \"schema\": {\r\n \"title\": \"Openstreet Map Configuration\",\r\n \"type\": \"object\",\r\n \"properties\": {\r\n \"mapProvider\": {\r\n \"title\": \"Map provider\",\r\n \"type\": \"string\",\r\n \"default\": \"OpenStreetMap.Mapnik\"\r\n },\r\n \"defaultZoomLevel\": {\r\n\t\t\t\t\t\"title\": \"Default map zoom level (1 - 20)\",\r\n\t\t\t\t\t\"type\": \"number\"\r\n\t\t\t\t},\r\n\t\t\t\"fitMapBounds\": {\r\n\t\t\t\t\"title\": \"Fit map bounds to cover all markers\",\r\n\t\t\t\t\"type\": \"boolean\",\r\n\t\t\t\t\"default\": true\r\n\t\t\t},\r\n \"latKeyName\": {\r\n \"title\": \"Latitude key name\",\r\n \"type\": \"string\",\r\n \"default\": \"latitude\"\r\n },\r\n \"lngKeyName\": {\r\n \"title\": \"Longitude key name\",\r\n \"type\": \"string\",\r\n \"default\": \"longitude\"\r\n },\r\n \"showLabel\": {\r\n \"title\": \"Show label\",\r\n \"type\": \"boolean\",\r\n \"default\": true\r\n },\r\n \"label\": {\r\n \"title\": \"Label (pattern examples: '${entityName}', '${entityName}: (Text ${keyName} units.)' )\",\r\n \"type\": \"string\",\r\n \"default\": \"${entityName}\"\r\n },\r\n \"useLabelFunction\": {\r\n \"title\": \"Use label function\",\r\n \"type\": \"boolean\",\r\n \"default\": false\r\n },\r\n \"labelFunction\": {\r\n \"title\": \"Label function: f(data, dsData, dsIndex)\",\r\n \"type\": \"string\"\r\n },\r\n \"showTooltip\": {\r\n \"title\": \"Show tooltip\",\r\n \"type\": \"boolean\",\r\n \"default\": true\r\n },\r\n \"tooltipColor\": {\r\n \"title\": \"Tooltip background color\",\r\n \"type\": \"string\",\r\n \"default\": \"#fff\"\r\n },\r\n \"tooltipFontColor\": {\r\n \"title\": \"Tooltip font color\",\r\n \"type\": \"string\",\r\n \"default\": \"#000\"\r\n },\r\n \"tooltipOpacity\": {\r\n \"title\": \"Tooltip opacity (0-1)\",\r\n \"type\": \"number\",\r\n \"default\": 1 \r\n },\r\n \"tooltipPattern\": {\r\n \"title\": \"Tooltip (for ex. 'Text ${keyName} units.' or <link-act name='my-action'>Link text</link-act>')\",\r\n \"type\": \"string\",\r\n \"default\": \"<b>${entityName}</b><br/><br/><b>Latitude:</b> ${latitude:7}<br/><b>Longitude:</b> ${longitude:7}<br/><b>End Time:</b> ${maxTime}<br/><b>Start Time:</b> ${minTime}\"\r\n },\r\n \"useTooltipFunction\": {\r\n \"title\": \"Use tooltip function\",\r\n \"type\": \"boolean\",\r\n \"default\": false\r\n },\r\n \"tooltipFunction\": {\r\n \"title\": \"Tooltip function: f(data, dsData, dsIndex)\",\r\n \"type\": \"string\"\r\n },\r\n \"color\": {\r\n \"title\": \"Stroke color\",\r\n \"type\": \"string\"\r\n },\r\n \"strokeWeight\": {\r\n \"title\": \"Stroke weight\",\r\n \"type\": \"number\",\r\n \"default\": 2\r\n },\r\n \"strokeOpacity\": {\r\n \"title\": \"Stroke opacity\",\r\n \"type\": \"number\",\r\n \"default\": 1\r\n },\r\n \"useColorFunction\": {\r\n \"title\": \"Use stroke color function\",\r\n \"type\": \"boolean\",\r\n \"default\": false\r\n },\r\n \"colorFunction\": {\r\n \"title\": \"Stroke color function: f(data, dsData, dsIndex)\",\r\n \"type\": \"string\"\r\n },\r\n \"showPoints\": {\r\n \"title\": \"Show points\",\r\n \"type\": \"boolean\",\r\n \"default\": false\r\n },\r\n \"pointColor\": {\r\n \"title\": \"Point color\",\r\n \"type\": \"string\"\r\n },\r\n \"pointSize\": {\r\n \"title\": \"Point size (px)\",\r\n \"type\": \"number\",\r\n \"default\": 10\r\n },\r\n \"defaultMarkerColor\": {\r\n \"title\": \"color for default marker\",\r\n \"type\": \"string\"\r\n },\r\n \"markerImage\": {\r\n \"title\": \"Custom marker image\",\r\n \"type\": \"string\"\r\n },\r\n \"markerImageSize\": {\r\n \"title\": \"Custom marker image size (px)\",\r\n \"type\": \"number\",\r\n \"default\": 34\r\n },\r\n \"rotationAngle\": {\r\n \"title\": \"Set additional rotation angle for marker (deg)\",\r\n \"type\": \"number\",\r\n \"default\": 180\r\n },\r\n \"useMarkerImageFunction\":{\r\n \"title\":\"Use marker image function\",\r\n \"type\":\"boolean\",\r\n \"default\":false\r\n },\r\n \"markerImageFunction\":{\r\n \"title\":\"Marker image function: f(data, images, dsData, dsIndex)\",\r\n \"type\":\"string\"\r\n },\r\n \"markerImages\":{\r\n \"title\":\"Marker images\",\r\n \"type\":\"array\",\r\n \"items\":{\r\n \"title\":\"Marker image\",\r\n \"type\":\"string\"\r\n }\r\n }\r\n },\r\n \"required\": []\r\n },\r\n \"form\": [{\r\n \"key\": \"mapProvider\",\r\n \"type\": \"rc-select\",\r\n \"multiple\": false,\r\n \"items\": [{\r\n \"value\": \"OpenStreetMap.Mapnik\",\r\n \"label\": \"OpenStreetMap.Mapnik (Default)\"\r\n }, {\r\n \"value\": \"OpenStreetMap.BlackAndWhite\",\r\n \"label\": \"OpenStreetMap.BlackAndWhite\"\r\n }, {\r\n \"value\": \"OpenStreetMap.HOT\",\r\n \"label\": \"OpenStreetMap.HOT\"\r\n }, {\r\n \"value\": \"Esri.WorldStreetMap\",\r\n \"label\": \"Esri.WorldStreetMap\"\r\n }, {\r\n \"value\": \"Esri.WorldTopoMap\",\r\n \"label\": \"Esri.WorldTopoMap\"\r\n }, {\r\n \"value\": \"CartoDB.Positron\",\r\n \"label\": \"CartoDB.Positron\"\r\n }, {\r\n \"value\": \"CartoDB.DarkMatter\",\r\n \"label\": \"CartoDB.DarkMatter\"\r\n }]\r\n },\"defaultZoomLevel\", \"fitMapBounds\", \"latKeyName\", \"lngKeyName\", \"showLabel\", \"label\", \"useLabelFunction\", {\r\n \"key\": \"labelFunction\",\r\n \"type\": \"javascript\"\r\n }, \"showTooltip\", {\r\n \"key\": \"tooltipColor\",\r\n \"type\": \"color\"\r\n }, {\r\n \"key\": \"tooltipFontColor\",\r\n \"type\": \"color\"\r\n },\"tooltipOpacity\", {\r\n \"key\": \"tooltipPattern\",\r\n \"type\": \"textarea\"\r\n }, \"useTooltipFunction\", {\r\n \"key\": \"tooltipFunction\",\r\n \"type\": \"javascript\"\r\n }, {\r\n \"key\": \"color\",\r\n \"type\": \"color\"\r\n }, \"useColorFunction\", {\r\n \"key\": \"colorFunction\",\r\n \"type\": \"javascript\"\r\n }, \"strokeWeight\", \"strokeOpacity\", \"showPoints\",{\r\n \"key\": \"pointColor\",\r\n \"type\": \"color\"\r\n }, \"pointSize\", {\r\n \"key\": \"defaultMarkerColor\",\r\n \"type\": \"color\"\r\n }, {\r\n \"key\": \"markerImage\",\r\n \"type\": \"image\"\r\n }, \"markerImageSize\", \"rotationAngle\",\"useMarkerImageFunction\",\r\n {\r\n \"key\":\"markerImageFunction\",\r\n \"type\":\"javascript\"\r\n }, {\r\n \"key\":\"markerImages\",\r\n \"items\":[\r\n {\r\n \"key\":\"markerImages[]\",\r\n \"type\":\"image\"\r\n }\r\n ]\r\n }]\r\n}", | ||
132 | + "dataKeySettingsSchema": "{}", | ||
133 | + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"Route\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var lats = [37.7696499,\\n37.7699074,\\n37.7699536,\\n37.7697242,\\n37.7695189,\\n37.7696889,\\n37.7697153,\\n37.7701244,\\n37.7700604,\\n37.7705491,\\n37.7715705,\\n37.771752,\\n37.7707533,\\n37.769866];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lats[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var lons = [-122.4261215,\\n-122.4219157,\\n-122.4199623,\\n-122.4179074,\\n-122.4155876,\\n-122.4155521,\\n-122.4163203,\\n-122.4193876,\\n-122.4210496,\\n-122.422284,\\n-122.4232717,\\n-122.4235138,\\n-122.4247605,\\n-122.4258812];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lons[i];\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"<span style=\\\"font-size: 26px; color: #666; font-weight: bold;\\\">${entityName}</span>\\n<br/>\\n<span style=\\\"font-size: 12px; color: #666; font-weight: bold;\\\">Time:</span><span style=\\\"font-size: 12px;\\\"> ${formattedTs}</span>\\n<span style=\\\"font-size: 12px; color: #666; font-weight: bold;\\\">Latitude:</span> ${latitude:7}\\n<span style=\\\"font-size: 12px; color: #666; font-weight: bold;\\\">Longitude:</span> ${longitude:7}\",\"strokeWeight\":3,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" | ||
134 | + } | ||
135 | + }, | ||
136 | + { | ||
121 | "alias": "route_map_heremap", | 137 | "alias": "route_map_heremap", |
122 | "name": "Route Map - HERE Map", | 138 | "name": "Route Map - HERE Map", |
123 | "descriptor": { | 139 | "descriptor": { |
@@ -48,11 +48,13 @@ import org.thingsboard.server.dao.alarm.AlarmService; | @@ -48,11 +48,13 @@ import org.thingsboard.server.dao.alarm.AlarmService; | ||
48 | import org.thingsboard.server.dao.asset.AssetService; | 48 | import org.thingsboard.server.dao.asset.AssetService; |
49 | import org.thingsboard.server.dao.attributes.AttributesService; | 49 | import org.thingsboard.server.dao.attributes.AttributesService; |
50 | import org.thingsboard.server.dao.audit.AuditLogService; | 50 | import org.thingsboard.server.dao.audit.AuditLogService; |
51 | +import org.thingsboard.server.dao.cassandra.CassandraCluster; | ||
51 | import org.thingsboard.server.dao.customer.CustomerService; | 52 | import org.thingsboard.server.dao.customer.CustomerService; |
52 | import org.thingsboard.server.dao.dashboard.DashboardService; | 53 | import org.thingsboard.server.dao.dashboard.DashboardService; |
53 | import org.thingsboard.server.dao.device.DeviceService; | 54 | import org.thingsboard.server.dao.device.DeviceService; |
54 | import org.thingsboard.server.dao.entityview.EntityViewService; | 55 | import org.thingsboard.server.dao.entityview.EntityViewService; |
55 | import org.thingsboard.server.dao.event.EventService; | 56 | import org.thingsboard.server.dao.event.EventService; |
57 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | ||
56 | import org.thingsboard.server.dao.relation.RelationService; | 58 | import org.thingsboard.server.dao.relation.RelationService; |
57 | import org.thingsboard.server.dao.rule.RuleChainService; | 59 | import org.thingsboard.server.dao.rule.RuleChainService; |
58 | import org.thingsboard.server.dao.tenant.TenantService; | 60 | import org.thingsboard.server.dao.tenant.TenantService; |
@@ -308,6 +310,16 @@ public class ActorSystemContext { | @@ -308,6 +310,16 @@ public class ActorSystemContext { | ||
308 | @Getter | 310 | @Getter |
309 | private final Config config; | 311 | private final Config config; |
310 | 312 | ||
313 | + @Autowired(required = false) | ||
314 | + @Getter | ||
315 | + private CassandraCluster cassandraCluster; | ||
316 | + | ||
317 | + @Autowired(required = false) | ||
318 | + @Getter | ||
319 | + private CassandraBufferedRateExecutor cassandraBufferedRateExecutor; | ||
320 | + | ||
321 | + | ||
322 | + | ||
311 | public ActorSystemContext() { | 323 | public ActorSystemContext() { |
312 | config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); | 324 | config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load()); |
313 | } | 325 | } |
@@ -118,17 +118,23 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | @@ -118,17 +118,23 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { | ||
118 | this.rpcSubscriptions = new HashMap<>(); | 118 | this.rpcSubscriptions = new HashMap<>(); |
119 | this.toDeviceRpcPendingMap = new HashMap<>(); | 119 | this.toDeviceRpcPendingMap = new HashMap<>(); |
120 | this.toServerRpcPendingMap = new HashMap<>(); | 120 | this.toServerRpcPendingMap = new HashMap<>(); |
121 | - initAttributes(); | ||
122 | - restoreSessions(); | 121 | + if (initAttributes()) { |
122 | + restoreSessions(); | ||
123 | + } | ||
123 | } | 124 | } |
124 | 125 | ||
125 | - private void initAttributes() { | 126 | + private boolean initAttributes() { |
126 | Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId); | 127 | Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId); |
127 | - this.deviceName = device.getName(); | ||
128 | - this.deviceType = device.getType(); | ||
129 | - this.defaultMetaData = new TbMsgMetaData(); | ||
130 | - this.defaultMetaData.putValue("deviceName", deviceName); | ||
131 | - this.defaultMetaData.putValue("deviceType", deviceType); | 128 | + if (device != null) { |
129 | + this.deviceName = device.getName(); | ||
130 | + this.deviceType = device.getType(); | ||
131 | + this.defaultMetaData = new TbMsgMetaData(); | ||
132 | + this.defaultMetaData.putValue("deviceName", deviceName); | ||
133 | + this.defaultMetaData.putValue("deviceType", deviceType); | ||
134 | + return true; | ||
135 | + } else { | ||
136 | + return false; | ||
137 | + } | ||
132 | } | 138 | } |
133 | 139 | ||
134 | void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) { | 140 | void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) { |
@@ -44,10 +44,12 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; | @@ -44,10 +44,12 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; | ||
44 | import org.thingsboard.server.dao.alarm.AlarmService; | 44 | import org.thingsboard.server.dao.alarm.AlarmService; |
45 | import org.thingsboard.server.dao.asset.AssetService; | 45 | import org.thingsboard.server.dao.asset.AssetService; |
46 | import org.thingsboard.server.dao.attributes.AttributesService; | 46 | import org.thingsboard.server.dao.attributes.AttributesService; |
47 | +import org.thingsboard.server.dao.cassandra.CassandraCluster; | ||
47 | import org.thingsboard.server.dao.customer.CustomerService; | 48 | import org.thingsboard.server.dao.customer.CustomerService; |
48 | import org.thingsboard.server.dao.dashboard.DashboardService; | 49 | import org.thingsboard.server.dao.dashboard.DashboardService; |
49 | import org.thingsboard.server.dao.device.DeviceService; | 50 | import org.thingsboard.server.dao.device.DeviceService; |
50 | import org.thingsboard.server.dao.entityview.EntityViewService; | 51 | import org.thingsboard.server.dao.entityview.EntityViewService; |
52 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | ||
51 | import org.thingsboard.server.dao.relation.RelationService; | 53 | import org.thingsboard.server.dao.relation.RelationService; |
52 | import org.thingsboard.server.dao.rule.RuleChainService; | 54 | import org.thingsboard.server.dao.rule.RuleChainService; |
53 | import org.thingsboard.server.dao.tenant.TenantService; | 55 | import org.thingsboard.server.dao.tenant.TenantService; |
@@ -292,4 +294,15 @@ class DefaultTbContext implements TbContext { | @@ -292,4 +294,15 @@ class DefaultTbContext implements TbContext { | ||
292 | } | 294 | } |
293 | }; | 295 | }; |
294 | } | 296 | } |
297 | + | ||
298 | + @Override | ||
299 | + public CassandraCluster getCassandraCluster() { | ||
300 | + return mainCtx.getCassandraCluster(); | ||
301 | + } | ||
302 | + | ||
303 | + @Override | ||
304 | + public CassandraBufferedRateExecutor getCassandraBufferedRateExecutor() { | ||
305 | + return mainCtx.getCassandraBufferedRateExecutor(); | ||
306 | + } | ||
307 | + | ||
295 | } | 308 | } |
@@ -91,17 +91,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -91,17 +91,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
91 | public void start(ActorContext context) { | 91 | public void start(ActorContext context) { |
92 | if (!started) { | 92 | if (!started) { |
93 | RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); | 93 | RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); |
94 | - ruleChainName = ruleChain.getName(); | ||
95 | - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId); | ||
96 | - log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); | ||
97 | - // Creating and starting the actors; | ||
98 | - for (RuleNode ruleNode : ruleNodeList) { | ||
99 | - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
100 | - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | ||
101 | - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | 94 | + if (ruleChain != null) { |
95 | + ruleChainName = ruleChain.getName(); | ||
96 | + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId); | ||
97 | + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); | ||
98 | + // Creating and starting the actors; | ||
99 | + for (RuleNode ruleNode : ruleNodeList) { | ||
100 | + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
101 | + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | ||
102 | + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | ||
103 | + } | ||
104 | + initRoutes(ruleChain, ruleNodeList); | ||
105 | + started = true; | ||
102 | } | 106 | } |
103 | - initRoutes(ruleChain, ruleNodeList); | ||
104 | - started = true; | ||
105 | } else { | 107 | } else { |
106 | onUpdate(context); | 108 | onUpdate(context); |
107 | } | 109 | } |
@@ -110,31 +112,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | @@ -110,31 +112,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh | ||
110 | @Override | 112 | @Override |
111 | public void onUpdate(ActorContext context) { | 113 | public void onUpdate(ActorContext context) { |
112 | RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); | 114 | RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); |
113 | - ruleChainName = ruleChain.getName(); | ||
114 | - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId); | ||
115 | - log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); | ||
116 | - for (RuleNode ruleNode : ruleNodeList) { | ||
117 | - RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); | ||
118 | - if (existing == null) { | ||
119 | - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
120 | - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | ||
121 | - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | ||
122 | - } else { | ||
123 | - log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
124 | - existing.setSelf(ruleNode); | ||
125 | - existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); | 115 | + if (ruleChain != null) { |
116 | + ruleChainName = ruleChain.getName(); | ||
117 | + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId); | ||
118 | + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); | ||
119 | + for (RuleNode ruleNode : ruleNodeList) { | ||
120 | + RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); | ||
121 | + if (existing == null) { | ||
122 | + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
123 | + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); | ||
124 | + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); | ||
125 | + } else { | ||
126 | + log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode); | ||
127 | + existing.setSelf(ruleNode); | ||
128 | + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); | ||
129 | + } | ||
126 | } | 130 | } |
127 | - } | ||
128 | 131 | ||
129 | - Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); | ||
130 | - List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); | ||
131 | - removedRules.forEach(ruleNodeId -> { | ||
132 | - log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); | ||
133 | - RuleNodeCtx removed = nodeActors.remove(ruleNodeId); | ||
134 | - removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); | ||
135 | - }); | 132 | + Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); |
133 | + List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); | ||
134 | + removedRules.forEach(ruleNodeId -> { | ||
135 | + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); | ||
136 | + RuleNodeCtx removed = nodeActors.remove(ruleNodeId); | ||
137 | + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); | ||
138 | + }); | ||
136 | 139 | ||
137 | - initRoutes(ruleChain, ruleNodeList); | 140 | + initRoutes(ruleChain, ruleNodeList); |
141 | + } | ||
138 | } | 142 | } |
139 | 143 | ||
140 | @Override | 144 | @Override |
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
@@ -55,7 +55,9 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -55,7 +55,9 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
55 | @Override | 55 | @Override |
56 | public void start(ActorContext context) throws Exception { | 56 | public void start(ActorContext context) throws Exception { |
57 | tbNode = initComponent(ruleNode); | 57 | tbNode = initComponent(ruleNode); |
58 | - state = ComponentLifecycleState.ACTIVE; | 58 | + if (tbNode != null) { |
59 | + state = ComponentLifecycleState.ACTIVE; | ||
60 | + } | ||
59 | } | 61 | } |
60 | 62 | ||
61 | @Override | 63 | @Override |
@@ -118,9 +120,12 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | @@ -118,9 +120,12 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod | ||
118 | } | 120 | } |
119 | 121 | ||
120 | private TbNode initComponent(RuleNode ruleNode) throws Exception { | 122 | private TbNode initComponent(RuleNode ruleNode) throws Exception { |
121 | - Class<?> componentClazz = Class.forName(ruleNode.getType()); | ||
122 | - TbNode tbNode = (TbNode) (componentClazz.newInstance()); | ||
123 | - tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration())); | 123 | + TbNode tbNode = null; |
124 | + if (ruleNode != null) { | ||
125 | + Class<?> componentClazz = Class.forName(ruleNode.getType()); | ||
126 | + tbNode = (TbNode) (componentClazz.newInstance()); | ||
127 | + tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration())); | ||
128 | + } | ||
124 | return tbNode; | 129 | return tbNode; |
125 | } | 130 | } |
126 | 131 |
@@ -22,7 +22,7 @@ import java.util.HashMap; | @@ -22,7 +22,7 @@ import java.util.HashMap; | ||
22 | import java.util.Map; | 22 | import java.util.Map; |
23 | 23 | ||
24 | @Configuration | 24 | @Configuration |
25 | -@ConfigurationProperties(prefix = "audit_log.logging_level") | 25 | +@ConfigurationProperties(prefix = "audit-log.logging-level") |
26 | public class AuditLogLevelProperties { | 26 | public class AuditLogLevelProperties { |
27 | 27 | ||
28 | private Map<String, String> mask = new HashMap<>(); | 28 | private Map<String, String> mask = new HashMap<>(); |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.config; | ||
17 | + | ||
18 | +import org.springframework.context.annotation.Bean; | ||
19 | +import org.springframework.context.annotation.Configuration; | ||
20 | +import org.springframework.scheduling.TaskScheduler; | ||
21 | +import org.springframework.scheduling.annotation.EnableScheduling; | ||
22 | +import org.springframework.scheduling.annotation.SchedulingConfigurer; | ||
23 | +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | ||
24 | +import org.springframework.scheduling.config.ScheduledTaskRegistrar; | ||
25 | + | ||
26 | +import java.util.concurrent.Executor; | ||
27 | +import java.util.concurrent.Executors; | ||
28 | + | ||
29 | +@Configuration | ||
30 | +@EnableScheduling | ||
31 | +public class SchedulingConfiguration implements SchedulingConfigurer { | ||
32 | + | ||
33 | + @Override | ||
34 | + public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { | ||
35 | + taskRegistrar.setScheduler(taskScheduler()); | ||
36 | + } | ||
37 | + | ||
38 | + @Bean(destroyMethod="shutdown") | ||
39 | + public TaskScheduler taskScheduler() { | ||
40 | + ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler(); | ||
41 | + threadPoolScheduler.setThreadNamePrefix("TB-Scheduling-"); | ||
42 | + threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors()); | ||
43 | + threadPoolScheduler.setRemoveOnCancelPolicy(true); | ||
44 | + return threadPoolScheduler; | ||
45 | + } | ||
46 | +} |
@@ -15,11 +15,28 @@ | @@ -15,11 +15,28 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.config; | 16 | package org.thingsboard.server.config; |
17 | 17 | ||
18 | +import lombok.extern.slf4j.Slf4j; | ||
19 | +import org.apache.commons.collections.ExtendedProperties; | ||
20 | +import org.apache.commons.logging.Log; | ||
21 | +import org.apache.commons.logging.LogFactory; | ||
22 | +import org.apache.velocity.app.VelocityEngine; | ||
23 | +import org.apache.velocity.exception.ResourceNotFoundException; | ||
24 | +import org.apache.velocity.runtime.RuntimeConstants; | ||
25 | +import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader; | ||
18 | import org.springframework.context.MessageSource; | 26 | import org.springframework.context.MessageSource; |
19 | import org.springframework.context.annotation.Bean; | 27 | import org.springframework.context.annotation.Bean; |
20 | import org.springframework.context.annotation.Configuration; | 28 | import org.springframework.context.annotation.Configuration; |
21 | import org.springframework.context.annotation.Primary; | 29 | import org.springframework.context.annotation.Primary; |
22 | import org.springframework.context.support.ResourceBundleMessageSource; | 30 | import org.springframework.context.support.ResourceBundleMessageSource; |
31 | +import org.springframework.core.io.DefaultResourceLoader; | ||
32 | +import org.springframework.core.io.Resource; | ||
33 | +import org.springframework.core.io.ResourceLoader; | ||
34 | +import org.springframework.util.StringUtils; | ||
35 | + | ||
36 | +import java.io.File; | ||
37 | +import java.io.IOException; | ||
38 | +import java.io.InputStream; | ||
39 | +import java.util.Arrays; | ||
23 | 40 | ||
24 | @Configuration | 41 | @Configuration |
25 | public class ThingsboardMessageConfiguration { | 42 | public class ThingsboardMessageConfiguration { |
@@ -32,5 +49,114 @@ public class ThingsboardMessageConfiguration { | @@ -32,5 +49,114 @@ public class ThingsboardMessageConfiguration { | ||
32 | messageSource.setDefaultEncoding("UTF-8"); | 49 | messageSource.setDefaultEncoding("UTF-8"); |
33 | return messageSource; | 50 | return messageSource; |
34 | } | 51 | } |
35 | - | 52 | + |
53 | + private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:/templates/"; | ||
54 | + | ||
55 | + private ResourceLoader resourceLoader = new DefaultResourceLoader(); | ||
56 | + | ||
57 | + @Bean | ||
58 | + public VelocityEngine velocityEngine() { | ||
59 | + VelocityEngine velocityEngine = new VelocityEngine(); | ||
60 | + try { | ||
61 | + Resource resource = resourceLoader.getResource(DEFAULT_RESOURCE_LOADER_PATH); | ||
62 | + File file = resource.getFile(); | ||
63 | + velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file"); | ||
64 | + velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true"); | ||
65 | + velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, file.getAbsolutePath()); | ||
66 | + } catch (IOException e) { | ||
67 | + initSpringResourceLoader(velocityEngine, DEFAULT_RESOURCE_LOADER_PATH); | ||
68 | + } | ||
69 | + velocityEngine.init(); | ||
70 | + return velocityEngine; | ||
71 | + } | ||
72 | + | ||
73 | + private void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) { | ||
74 | + velocityEngine.setProperty( | ||
75 | + RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME); | ||
76 | + velocityEngine.setProperty( | ||
77 | + SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName()); | ||
78 | + velocityEngine.setProperty( | ||
79 | + SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true"); | ||
80 | + velocityEngine.setApplicationAttribute( | ||
81 | + SpringResourceLoader.SPRING_RESOURCE_LOADER, resourceLoader); | ||
82 | + velocityEngine.setApplicationAttribute( | ||
83 | + SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath); | ||
84 | + } | ||
85 | + | ||
86 | + @Slf4j | ||
87 | + static class SpringResourceLoader extends org.apache.velocity.runtime.resource.loader.ResourceLoader { | ||
88 | + | ||
89 | + public static final String NAME = "spring"; | ||
90 | + | ||
91 | + public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class"; | ||
92 | + | ||
93 | + public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache"; | ||
94 | + | ||
95 | + public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader"; | ||
96 | + | ||
97 | + public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path"; | ||
98 | + | ||
99 | + private org.springframework.core.io.ResourceLoader resourceLoader; | ||
100 | + | ||
101 | + private String[] resourceLoaderPaths; | ||
102 | + | ||
103 | + | ||
104 | + @Override | ||
105 | + public void init(ExtendedProperties configuration) { | ||
106 | + this.resourceLoader = (org.springframework.core.io.ResourceLoader) | ||
107 | + this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER); | ||
108 | + String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH); | ||
109 | + if (this.resourceLoader == null) { | ||
110 | + throw new IllegalArgumentException( | ||
111 | + "'resourceLoader' application attribute must be present for SpringResourceLoader"); | ||
112 | + } | ||
113 | + if (resourceLoaderPath == null) { | ||
114 | + throw new IllegalArgumentException( | ||
115 | + "'resourceLoaderPath' application attribute must be present for SpringResourceLoader"); | ||
116 | + } | ||
117 | + this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath); | ||
118 | + for (int i = 0; i < this.resourceLoaderPaths.length; i++) { | ||
119 | + String path = this.resourceLoaderPaths[i]; | ||
120 | + if (!path.endsWith("/")) { | ||
121 | + this.resourceLoaderPaths[i] = path + "/"; | ||
122 | + } | ||
123 | + } | ||
124 | + if (log.isInfoEnabled()) { | ||
125 | + log.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader + | ||
126 | + "] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths)); | ||
127 | + } | ||
128 | + } | ||
129 | + | ||
130 | + @Override | ||
131 | + public InputStream getResourceStream(String source) throws ResourceNotFoundException { | ||
132 | + if (log.isDebugEnabled()) { | ||
133 | + log.debug("Looking for Velocity resource with name [" + source + "]"); | ||
134 | + } | ||
135 | + for (String resourceLoaderPath : this.resourceLoaderPaths) { | ||
136 | + org.springframework.core.io.Resource resource = | ||
137 | + this.resourceLoader.getResource(resourceLoaderPath + source); | ||
138 | + try { | ||
139 | + return resource.getInputStream(); | ||
140 | + } | ||
141 | + catch (IOException ex) { | ||
142 | + if (log.isDebugEnabled()) { | ||
143 | + log.debug("Could not find Velocity resource: " + resource); | ||
144 | + } | ||
145 | + } | ||
146 | + } | ||
147 | + throw new ResourceNotFoundException( | ||
148 | + "Could not find resource [" + source + "] in Spring resource loader path"); | ||
149 | + } | ||
150 | + | ||
151 | + @Override | ||
152 | + public boolean isSourceModified(org.apache.velocity.runtime.resource.Resource resource) { | ||
153 | + return false; | ||
154 | + } | ||
155 | + | ||
156 | + @Override | ||
157 | + public long getLastModified(org.apache.velocity.runtime.resource.Resource resource) { | ||
158 | + return 0; | ||
159 | + } | ||
160 | + | ||
161 | + } | ||
36 | } | 162 | } |
@@ -57,7 +57,7 @@ import java.util.List; | @@ -57,7 +57,7 @@ import java.util.List; | ||
57 | @Configuration | 57 | @Configuration |
58 | @EnableWebSecurity | 58 | @EnableWebSecurity |
59 | @EnableGlobalMethodSecurity(prePostEnabled=true) | 59 | @EnableGlobalMethodSecurity(prePostEnabled=true) |
60 | -@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER) | 60 | +@Order(SecurityProperties.BASIC_AUTH_ORDER) |
61 | public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter { | 61 | public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter { |
62 | 62 | ||
63 | public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; | 63 | public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; |
@@ -58,7 +58,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { | @@ -58,7 +58,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { | ||
58 | 58 | ||
59 | @Override | 59 | @Override |
60 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, | 60 | public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, |
61 | - Map<String, Object> attributes) throws Exception { | 61 | + Map<String, Object> attributes) throws Exception { |
62 | SecurityUser user = null; | 62 | SecurityUser user = null; |
63 | try { | 63 | try { |
64 | user = getCurrentUser(); | 64 | user = getCurrentUser(); |
@@ -73,7 +73,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { | @@ -73,7 +73,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { | ||
73 | 73 | ||
74 | @Override | 74 | @Override |
75 | public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, | 75 | public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, |
76 | - Exception exception) { | 76 | + Exception exception) { |
77 | //Do nothing | 77 | //Do nothing |
78 | } | 78 | } |
79 | }); | 79 | }); |
@@ -124,6 +124,7 @@ public class ThingsboardInstallService { | @@ -124,6 +124,7 @@ public class ThingsboardInstallService { | ||
124 | systemDataLoaderService.deleteSystemWidgetBundle("maps_v2"); | 124 | systemDataLoaderService.deleteSystemWidgetBundle("maps_v2"); |
125 | systemDataLoaderService.deleteSystemWidgetBundle("gateway_widgets"); | 125 | systemDataLoaderService.deleteSystemWidgetBundle("gateway_widgets"); |
126 | systemDataLoaderService.deleteSystemWidgetBundle("input_widgets"); | 126 | systemDataLoaderService.deleteSystemWidgetBundle("input_widgets"); |
127 | + systemDataLoaderService.deleteSystemWidgetBundle("date"); | ||
127 | 128 | ||
128 | systemDataLoaderService.loadSystemWidgets(); | 129 | systemDataLoaderService.loadSystemWidgets(); |
129 | break; | 130 | break; |
@@ -52,6 +52,8 @@ import javax.annotation.PostConstruct; | @@ -52,6 +52,8 @@ import javax.annotation.PostConstruct; | ||
52 | import javax.annotation.PreDestroy; | 52 | import javax.annotation.PreDestroy; |
53 | import java.util.List; | 53 | import java.util.List; |
54 | import java.util.NoSuchElementException; | 54 | import java.util.NoSuchElementException; |
55 | +import java.util.concurrent.ExecutorService; | ||
56 | +import java.util.concurrent.Executors; | ||
55 | import java.util.stream.Collectors; | 57 | import java.util.stream.Collectors; |
56 | 58 | ||
57 | import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; | 59 | import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; |
@@ -96,11 +98,13 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -96,11 +98,13 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
96 | @Lazy | 98 | @Lazy |
97 | private ClusterRoutingService routingService; | 99 | private ClusterRoutingService routingService; |
98 | 100 | ||
101 | + private ExecutorService reconnectExecutorService; | ||
102 | + | ||
99 | private CuratorFramework client; | 103 | private CuratorFramework client; |
100 | private PathChildrenCache cache; | 104 | private PathChildrenCache cache; |
101 | private String nodePath; | 105 | private String nodePath; |
102 | 106 | ||
103 | - private volatile boolean stopped = false; | 107 | + private volatile boolean stopped = true; |
104 | 108 | ||
105 | @PostConstruct | 109 | @PostConstruct |
106 | public void init() { | 110 | public void init() { |
@@ -110,9 +114,15 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -110,9 +114,15 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
110 | Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); | 114 | Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); |
111 | Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); | 115 | Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); |
112 | 116 | ||
117 | + reconnectExecutorService = Executors.newSingleThreadExecutor(); | ||
118 | + | ||
113 | log.info("Initializing discovery service using ZK connect string: {}", zkUrl); | 119 | log.info("Initializing discovery service using ZK connect string: {}", zkUrl); |
114 | 120 | ||
115 | zkNodesDir = zkDir + "/nodes"; | 121 | zkNodesDir = zkDir + "/nodes"; |
122 | + initZkClient(); | ||
123 | + } | ||
124 | + | ||
125 | + private void initZkClient() { | ||
116 | try { | 126 | try { |
117 | client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval)); | 127 | client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval)); |
118 | client.start(); | 128 | client.start(); |
@@ -120,6 +130,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -120,6 +130,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
120 | cache = new PathChildrenCache(client, zkNodesDir, true); | 130 | cache = new PathChildrenCache(client, zkNodesDir, true); |
121 | cache.getListenable().addListener(this); | 131 | cache.getListenable().addListener(this); |
122 | cache.start(); | 132 | cache.start(); |
133 | + stopped = false; | ||
134 | + log.info("ZK client connected"); | ||
123 | } catch (Exception e) { | 135 | } catch (Exception e) { |
124 | log.error("Failed to connect to ZK: {}", e.getMessage(), e); | 136 | log.error("Failed to connect to ZK: {}", e.getMessage(), e); |
125 | CloseableUtils.closeQuietly(cache); | 137 | CloseableUtils.closeQuietly(cache); |
@@ -128,12 +140,20 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -128,12 +140,20 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
128 | } | 140 | } |
129 | } | 141 | } |
130 | 142 | ||
131 | - @PreDestroy | ||
132 | - public void destroy() { | 143 | + private void destroyZkClient() { |
133 | stopped = true; | 144 | stopped = true; |
134 | - unpublishCurrentServer(); | 145 | + try { |
146 | + unpublishCurrentServer(); | ||
147 | + } catch (Exception e) {} | ||
135 | CloseableUtils.closeQuietly(cache); | 148 | CloseableUtils.closeQuietly(cache); |
136 | CloseableUtils.closeQuietly(client); | 149 | CloseableUtils.closeQuietly(client); |
150 | + log.info("ZK client disconnected"); | ||
151 | + } | ||
152 | + | ||
153 | + @PreDestroy | ||
154 | + public void destroy() { | ||
155 | + destroyZkClient(); | ||
156 | + reconnectExecutorService.shutdownNow(); | ||
137 | log.info("Stopped discovery service"); | 157 | log.info("Stopped discovery service"); |
138 | } | 158 | } |
139 | 159 | ||
@@ -180,20 +200,21 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | @@ -180,20 +200,21 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi | ||
180 | return (client, newState) -> { | 200 | return (client, newState) -> { |
181 | log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState); | 201 | log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState); |
182 | if (newState == ConnectionState.LOST) { | 202 | if (newState == ConnectionState.LOST) { |
183 | - reconnect(); | 203 | + reconnectExecutorService.submit(this::reconnect); |
184 | } | 204 | } |
185 | }; | 205 | }; |
186 | } | 206 | } |
187 | 207 | ||
188 | - private boolean reconnectInProgress = false; | 208 | + private volatile boolean reconnectInProgress = false; |
189 | 209 | ||
190 | private synchronized void reconnect() { | 210 | private synchronized void reconnect() { |
191 | if (!reconnectInProgress) { | 211 | if (!reconnectInProgress) { |
192 | reconnectInProgress = true; | 212 | reconnectInProgress = true; |
193 | try { | 213 | try { |
194 | - client.blockUntilConnected(); | 214 | + destroyZkClient(); |
215 | + initZkClient(); | ||
195 | publishCurrentServer(); | 216 | publishCurrentServer(); |
196 | - } catch (InterruptedException e) { | 217 | + } catch (Exception e) { |
197 | log.error("Failed to reconnect to ZK: {}", e.getMessage(), e); | 218 | log.error("Failed to reconnect to ZK: {}", e.getMessage(), e); |
198 | } finally { | 219 | } finally { |
199 | reconnectInProgress = false; | 220 | reconnectInProgress = false; |
@@ -95,15 +95,24 @@ public final class GrpcSession implements Closeable { | @@ -95,15 +95,24 @@ public final class GrpcSession implements Closeable { | ||
95 | } | 95 | } |
96 | 96 | ||
97 | public void sendMsg(ClusterAPIProtos.ClusterMessage msg) { | 97 | public void sendMsg(ClusterAPIProtos.ClusterMessage msg) { |
98 | - outputStream.onNext(msg); | ||
99 | - } | ||
100 | - | ||
101 | - public void onError(Throwable t) { | ||
102 | - outputStream.onError(t); | 98 | + if (connected) { |
99 | + try { | ||
100 | + outputStream.onNext(msg); | ||
101 | + } catch (Throwable t) { | ||
102 | + try { | ||
103 | + outputStream.onError(t); | ||
104 | + } catch (Throwable t2) { | ||
105 | + } | ||
106 | + listener.onError(GrpcSession.this, t); | ||
107 | + } | ||
108 | + } else { | ||
109 | + log.warn("[{}] Failed to send message due to closed session!", sessionId); | ||
110 | + } | ||
103 | } | 111 | } |
104 | 112 | ||
105 | @Override | 113 | @Override |
106 | public void close() { | 114 | public void close() { |
115 | + connected = false; | ||
107 | try { | 116 | try { |
108 | outputStream.onCompleted(); | 117 | outputStream.onCompleted(); |
109 | } catch (IllegalStateException e) { | 118 | } catch (IllegalStateException e) { |
@@ -17,6 +17,7 @@ package org.thingsboard.server.service.install; | @@ -17,6 +17,7 @@ package org.thingsboard.server.service.install; | ||
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.beans.factory.annotation.Autowired; | 19 | import org.springframework.beans.factory.annotation.Autowired; |
20 | +import org.springframework.beans.factory.annotation.Qualifier; | ||
20 | import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; | 21 | import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; |
21 | import org.thingsboard.server.service.install.cql.CQLStatementsParser; | 22 | import org.thingsboard.server.service.install.cql.CQLStatementsParser; |
22 | 23 | ||
@@ -30,6 +31,7 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database | @@ -30,6 +31,7 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database | ||
30 | private static final String CASSANDRA_DIR = "cassandra"; | 31 | private static final String CASSANDRA_DIR = "cassandra"; |
31 | 32 | ||
32 | @Autowired | 33 | @Autowired |
34 | + @Qualifier("CassandraInstallCluster") | ||
33 | private CassandraInstallCluster cluster; | 35 | private CassandraInstallCluster cluster; |
34 | 36 | ||
35 | @Autowired | 37 | @Autowired |
@@ -18,6 +18,7 @@ package org.thingsboard.server.service.install; | @@ -18,6 +18,7 @@ package org.thingsboard.server.service.install; | ||
18 | import com.datastax.driver.core.KeyspaceMetadata; | 18 | import com.datastax.driver.core.KeyspaceMetadata; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | import org.springframework.beans.factory.annotation.Autowired; | 20 | import org.springframework.beans.factory.annotation.Autowired; |
21 | +import org.springframework.beans.factory.annotation.Qualifier; | ||
21 | import org.springframework.context.annotation.Profile; | 22 | import org.springframework.context.annotation.Profile; |
22 | import org.springframework.stereotype.Service; | 23 | import org.springframework.stereotype.Service; |
23 | import org.thingsboard.server.dao.cassandra.CassandraCluster; | 24 | import org.thingsboard.server.dao.cassandra.CassandraCluster; |
@@ -65,6 +66,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { | @@ -65,6 +66,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { | ||
65 | private CassandraCluster cluster; | 66 | private CassandraCluster cluster; |
66 | 67 | ||
67 | @Autowired | 68 | @Autowired |
69 | + @Qualifier("CassandraInstallCluster") | ||
68 | private CassandraInstallCluster installCluster; | 70 | private CassandraInstallCluster installCluster; |
69 | 71 | ||
70 | @Autowired | 72 | @Autowired |
@@ -18,7 +18,9 @@ package org.thingsboard.server.service.mail; | @@ -18,7 +18,9 @@ package org.thingsboard.server.service.mail; | ||
18 | import com.fasterxml.jackson.databind.JsonNode; | 18 | import com.fasterxml.jackson.databind.JsonNode; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | import org.apache.commons.lang3.StringUtils; | 20 | import org.apache.commons.lang3.StringUtils; |
21 | +import org.apache.velocity.VelocityContext; | ||
21 | import org.apache.velocity.app.VelocityEngine; | 22 | import org.apache.velocity.app.VelocityEngine; |
23 | +import org.apache.velocity.exception.VelocityException; | ||
22 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
23 | import org.springframework.beans.factory.annotation.Qualifier; | 25 | import org.springframework.beans.factory.annotation.Qualifier; |
24 | import org.springframework.context.MessageSource; | 26 | import org.springframework.context.MessageSource; |
@@ -26,7 +28,6 @@ import org.springframework.core.NestedRuntimeException; | @@ -26,7 +28,6 @@ import org.springframework.core.NestedRuntimeException; | ||
26 | import org.springframework.mail.javamail.JavaMailSenderImpl; | 28 | import org.springframework.mail.javamail.JavaMailSenderImpl; |
27 | import org.springframework.mail.javamail.MimeMessageHelper; | 29 | import org.springframework.mail.javamail.MimeMessageHelper; |
28 | import org.springframework.stereotype.Service; | 30 | import org.springframework.stereotype.Service; |
29 | -import org.springframework.ui.velocity.VelocityEngineUtils; | ||
30 | import org.thingsboard.rule.engine.api.MailService; | 31 | import org.thingsboard.rule.engine.api.MailService; |
31 | import org.thingsboard.server.common.data.AdminSettings; | 32 | import org.thingsboard.server.common.data.AdminSettings; |
32 | import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; | 33 | import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; |
@@ -39,6 +40,8 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; | @@ -39,6 +40,8 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; | ||
39 | import javax.annotation.PostConstruct; | 40 | import javax.annotation.PostConstruct; |
40 | import javax.mail.MessagingException; | 41 | import javax.mail.MessagingException; |
41 | import javax.mail.internet.MimeMessage; | 42 | import javax.mail.internet.MimeMessage; |
43 | +import java.io.StringWriter; | ||
44 | +import java.io.Writer; | ||
42 | import java.util.HashMap; | 45 | import java.util.HashMap; |
43 | import java.util.Locale; | 46 | import java.util.Locale; |
44 | import java.util.Map; | 47 | import java.util.Map; |
@@ -126,7 +129,7 @@ public class DefaultMailService implements MailService { | @@ -126,7 +129,7 @@ public class DefaultMailService implements MailService { | ||
126 | Map<String, Object> model = new HashMap<String, Object>(); | 129 | Map<String, Object> model = new HashMap<String, Object>(); |
127 | model.put(TARGET_EMAIL, email); | 130 | model.put(TARGET_EMAIL, email); |
128 | 131 | ||
129 | - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine, | 132 | + String message = mergeTemplateIntoString(this.engine, |
130 | "test.vm", UTF_8, model); | 133 | "test.vm", UTF_8, model); |
131 | 134 | ||
132 | sendMail(testMailSender, mailFrom, email, subject, message); | 135 | sendMail(testMailSender, mailFrom, email, subject, message); |
@@ -141,7 +144,7 @@ public class DefaultMailService implements MailService { | @@ -141,7 +144,7 @@ public class DefaultMailService implements MailService { | ||
141 | model.put("activationLink", activationLink); | 144 | model.put("activationLink", activationLink); |
142 | model.put(TARGET_EMAIL, email); | 145 | model.put(TARGET_EMAIL, email); |
143 | 146 | ||
144 | - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine, | 147 | + String message = mergeTemplateIntoString(this.engine, |
145 | "activation.vm", UTF_8, model); | 148 | "activation.vm", UTF_8, model); |
146 | 149 | ||
147 | sendMail(mailSender, mailFrom, email, subject, message); | 150 | sendMail(mailSender, mailFrom, email, subject, message); |
@@ -156,7 +159,7 @@ public class DefaultMailService implements MailService { | @@ -156,7 +159,7 @@ public class DefaultMailService implements MailService { | ||
156 | model.put("loginLink", loginLink); | 159 | model.put("loginLink", loginLink); |
157 | model.put(TARGET_EMAIL, email); | 160 | model.put(TARGET_EMAIL, email); |
158 | 161 | ||
159 | - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine, | 162 | + String message = mergeTemplateIntoString(this.engine, |
160 | "account.activated.vm", UTF_8, model); | 163 | "account.activated.vm", UTF_8, model); |
161 | 164 | ||
162 | sendMail(mailSender, mailFrom, email, subject, message); | 165 | sendMail(mailSender, mailFrom, email, subject, message); |
@@ -171,7 +174,7 @@ public class DefaultMailService implements MailService { | @@ -171,7 +174,7 @@ public class DefaultMailService implements MailService { | ||
171 | model.put("passwordResetLink", passwordResetLink); | 174 | model.put("passwordResetLink", passwordResetLink); |
172 | model.put(TARGET_EMAIL, email); | 175 | model.put(TARGET_EMAIL, email); |
173 | 176 | ||
174 | - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine, | 177 | + String message = mergeTemplateIntoString(this.engine, |
175 | "reset.password.vm", UTF_8, model); | 178 | "reset.password.vm", UTF_8, model); |
176 | 179 | ||
177 | sendMail(mailSender, mailFrom, email, subject, message); | 180 | sendMail(mailSender, mailFrom, email, subject, message); |
@@ -186,7 +189,7 @@ public class DefaultMailService implements MailService { | @@ -186,7 +189,7 @@ public class DefaultMailService implements MailService { | ||
186 | model.put("loginLink", loginLink); | 189 | model.put("loginLink", loginLink); |
187 | model.put(TARGET_EMAIL, email); | 190 | model.put(TARGET_EMAIL, email); |
188 | 191 | ||
189 | - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine, | 192 | + String message = mergeTemplateIntoString(this.engine, |
190 | "password.was.reset.vm", UTF_8, model); | 193 | "password.was.reset.vm", UTF_8, model); |
191 | 194 | ||
192 | sendMail(mailSender, mailFrom, email, subject, message); | 195 | sendMail(mailSender, mailFrom, email, subject, message); |
@@ -225,6 +228,22 @@ public class DefaultMailService implements MailService { | @@ -225,6 +228,22 @@ public class DefaultMailService implements MailService { | ||
225 | } | 228 | } |
226 | } | 229 | } |
227 | 230 | ||
231 | + private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation, | ||
232 | + String encoding, Map<String, Object> model) throws VelocityException { | ||
233 | + | ||
234 | + StringWriter result = new StringWriter(); | ||
235 | + mergeTemplate(velocityEngine, templateLocation, encoding, model, result); | ||
236 | + return result.toString(); | ||
237 | + } | ||
238 | + | ||
239 | + private static void mergeTemplate( | ||
240 | + VelocityEngine velocityEngine, String templateLocation, String encoding, | ||
241 | + Map<String, Object> model, Writer writer) throws VelocityException { | ||
242 | + | ||
243 | + VelocityContext velocityContext = new VelocityContext(model); | ||
244 | + velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer); | ||
245 | + } | ||
246 | + | ||
228 | protected ThingsboardException handleException(Exception exception) { | 247 | protected ThingsboardException handleException(Exception exception) { |
229 | String message; | 248 | String message; |
230 | if (exception instanceof NestedRuntimeException) { | 249 | if (exception instanceof NestedRuntimeException) { |
@@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.RpcError; | @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.RpcError; | ||
28 | import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; | 28 | import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; |
29 | import org.thingsboard.server.actors.service.ActorService; | 29 | import org.thingsboard.server.actors.service.ActorService; |
30 | import org.thingsboard.server.common.data.DataConstants; | 30 | import org.thingsboard.server.common.data.DataConstants; |
31 | +import org.thingsboard.server.common.data.Device; | ||
31 | import org.thingsboard.server.common.data.id.DeviceId; | 32 | import org.thingsboard.server.common.data.id.DeviceId; |
32 | import org.thingsboard.server.common.data.id.TenantId; | 33 | import org.thingsboard.server.common.data.id.TenantId; |
33 | import org.thingsboard.server.common.msg.TbMsg; | 34 | import org.thingsboard.server.common.msg.TbMsg; |
@@ -38,6 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; | @@ -38,6 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; | ||
38 | import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; | 39 | import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; |
39 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; | 40 | import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
40 | import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; | 41 | import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; |
42 | +import org.thingsboard.server.dao.device.DeviceService; | ||
41 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; | 43 | import org.thingsboard.server.gen.cluster.ClusterAPIProtos; |
42 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; | 44 | import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
43 | import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; | 45 | import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
@@ -68,6 +70,9 @@ public class DefaultDeviceRpcService implements DeviceRpcService { | @@ -68,6 +70,9 @@ public class DefaultDeviceRpcService implements DeviceRpcService { | ||
68 | private ClusterRpcService rpcService; | 70 | private ClusterRpcService rpcService; |
69 | 71 | ||
70 | @Autowired | 72 | @Autowired |
73 | + private DeviceService deviceService; | ||
74 | + | ||
75 | + @Autowired | ||
71 | @Lazy | 76 | @Lazy |
72 | private ActorService actorService; | 77 | private ActorService actorService; |
73 | 78 | ||
@@ -171,6 +176,12 @@ public class DefaultDeviceRpcService implements DeviceRpcService { | @@ -171,6 +176,12 @@ public class DefaultDeviceRpcService implements DeviceRpcService { | ||
171 | metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); | 176 | metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); |
172 | metaData.putValue("oneway", Boolean.toString(msg.isOneway())); | 177 | metaData.putValue("oneway", Boolean.toString(msg.isOneway())); |
173 | 178 | ||
179 | + Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId()); | ||
180 | + if (device != null) { | ||
181 | + metaData.putValue("deviceName", device.getName()); | ||
182 | + metaData.putValue("deviceType", device.getType()); | ||
183 | + } | ||
184 | + | ||
174 | entityNode.put("method", msg.getBody().getMethod()); | 185 | entityNode.put("method", msg.getBody().getMethod()); |
175 | entityNode.put("params", msg.getBody().getParams()); | 186 | entityNode.put("params", msg.getBody().getParams()); |
176 | 187 |
@@ -47,11 +47,4 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi | @@ -47,11 +47,4 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi | ||
47 | log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions); | 47 | log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions); |
48 | return sessions; | 48 | return sessions; |
49 | } | 49 | } |
50 | - | ||
51 | - public static void main (String[] args){ | ||
52 | - UUID uuid = UUID.fromString("d5db434e-9cd2-4903-8b3b-421b2d93664d"); | ||
53 | - System.out.println(uuid.getMostSignificantBits()); | ||
54 | - System.out.println(uuid.getLeastSignificantBits()); | ||
55 | - } | ||
56 | - | ||
57 | } | 50 | } |
@@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Value; | @@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Value; | ||
27 | import org.springframework.stereotype.Service; | 27 | import org.springframework.stereotype.Service; |
28 | import org.springframework.util.StringUtils; | 28 | import org.springframework.util.StringUtils; |
29 | import org.springframework.web.socket.CloseStatus; | 29 | import org.springframework.web.socket.CloseStatus; |
30 | -import org.springframework.web.socket.WebSocketSession; | ||
31 | import org.thingsboard.server.common.data.DataConstants; | 30 | import org.thingsboard.server.common.data.DataConstants; |
32 | import org.thingsboard.server.common.data.id.CustomerId; | 31 | import org.thingsboard.server.common.data.id.CustomerId; |
33 | import org.thingsboard.server.common.data.id.EntityId; | 32 | import org.thingsboard.server.common.data.id.EntityId; |
@@ -273,31 +273,27 @@ updates: | @@ -273,31 +273,27 @@ updates: | ||
273 | spring.mvc.cors: | 273 | spring.mvc.cors: |
274 | mappings: | 274 | mappings: |
275 | # Intercept path | 275 | # Intercept path |
276 | - "/api/auth/**": | 276 | + "[/api/**]": |
277 | #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled. | 277 | #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled. |
278 | allowed-origins: "*" | 278 | allowed-origins: "*" |
279 | #Comma-separated list of methods to allow. '*' allows all methods. | 279 | #Comma-separated list of methods to allow. '*' allows all methods. |
280 | - allowed-methods: "POST,GET,OPTIONS" | 280 | + allowed-methods: "*" |
281 | #Comma-separated list of headers to allow in a request. '*' allows all headers. | 281 | #Comma-separated list of headers to allow in a request. '*' allows all headers. |
282 | allowed-headers: "*" | 282 | allowed-headers: "*" |
283 | #How long, in seconds, the response from a pre-flight request can be cached by clients. | 283 | #How long, in seconds, the response from a pre-flight request can be cached by clients. |
284 | max-age: "1800" | 284 | max-age: "1800" |
285 | #Set whether credentials are supported. When not set, credentials are not supported. | 285 | #Set whether credentials are supported. When not set, credentials are not supported. |
286 | allow-credentials: "true" | 286 | allow-credentials: "true" |
287 | - "/api/v1/**": | ||
288 | - allowed-origins: "*" | ||
289 | - allowed-methods: "*" | ||
290 | - allowed-headers: "*" | ||
291 | - max-age: "1800" | ||
292 | - allow-credentials: "true" | ||
293 | 287 | ||
294 | # spring serve gzip compressed static resources | 288 | # spring serve gzip compressed static resources |
295 | spring.resources.chain: | 289 | spring.resources.chain: |
296 | - gzipped: "true" | 290 | + compressed: "true" |
297 | strategy: | 291 | strategy: |
298 | content: | 292 | content: |
299 | enabled: "true" | 293 | enabled: "true" |
300 | 294 | ||
295 | +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" | ||
296 | + | ||
301 | # HSQLDB DAO Configuration | 297 | # HSQLDB DAO Configuration |
302 | spring: | 298 | spring: |
303 | data: | 299 | data: |
@@ -331,7 +327,7 @@ spring: | @@ -331,7 +327,7 @@ spring: | ||
331 | # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" | 327 | # password: "${SPRING_DATASOURCE_PASSWORD:postgres}" |
332 | 328 | ||
333 | # Audit log parameters | 329 | # Audit log parameters |
334 | -audit_log: | 330 | +audit-log: |
335 | # Enable/disable audit log functionality. | 331 | # Enable/disable audit log functionality. |
336 | enabled: "${AUDIT_LOG_ENABLED:true}" | 332 | enabled: "${AUDIT_LOG_ENABLED:true}" |
337 | # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS | 333 | # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS |
@@ -340,7 +336,7 @@ audit_log: | @@ -340,7 +336,7 @@ audit_log: | ||
340 | default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" | 336 | default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" |
341 | # Logging levels per each entity type. | 337 | # Logging levels per each entity type. |
342 | # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) | 338 | # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) |
343 | - logging_level: | 339 | + logging-level: |
344 | mask: | 340 | mask: |
345 | "device": "${AUDIT_LOG_MASK_DEVICE:W}" | 341 | "device": "${AUDIT_LOG_MASK_DEVICE:W}" |
346 | "asset": "${AUDIT_LOG_MASK_ASSET:W}" | 342 | "asset": "${AUDIT_LOG_MASK_ASSET:W}" |
@@ -128,7 +128,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC | @@ -128,7 +128,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC | ||
128 | String accessToken = deviceCredentials.getCredentialsId(); | 128 | String accessToken = deviceCredentials.getCredentialsId(); |
129 | assertNotNull(accessToken); | 129 | assertNotNull(accessToken); |
130 | 130 | ||
131 | - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; | 131 | + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1},\"timeout\": 6000}"; |
132 | String deviceId = savedDevice.getId().getId().toString(); | 132 | String deviceId = savedDevice.getId().getId().toString(); |
133 | 133 | ||
134 | doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), | 134 | doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), |
@@ -183,7 +183,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC | @@ -183,7 +183,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC | ||
183 | String accessToken = deviceCredentials.getCredentialsId(); | 183 | String accessToken = deviceCredentials.getCredentialsId(); |
184 | assertNotNull(accessToken); | 184 | assertNotNull(accessToken); |
185 | 185 | ||
186 | - String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; | 186 | + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1},\"timeout\": 6000}"; |
187 | String deviceId = savedDevice.getId().getId().toString(); | 187 | String deviceId = savedDevice.getId().getId().toString(); |
188 | 188 | ||
189 | doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), | 189 | doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), |
@@ -111,7 +111,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr | @@ -111,7 +111,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr | ||
111 | client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value()); | 111 | client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value()); |
112 | String payload = "{\"key\":\"value\"}"; | 112 | String payload = "{\"key\":\"value\"}"; |
113 | String result = doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk()); | 113 | String result = doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk()); |
114 | - latch.await(3, TimeUnit.SECONDS); | 114 | + latch.await(10, TimeUnit.SECONDS); |
115 | assertEquals(payload, callback.getPayload()); | 115 | assertEquals(payload, callback.getPayload()); |
116 | assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); | 116 | assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); |
117 | } | 117 | } |
@@ -139,11 +139,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { | @@ -139,11 +139,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { | ||
139 | } | 139 | } |
140 | 140 | ||
141 | @Override | 141 | @Override |
142 | - public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { | 142 | + public Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { |
143 | if (!StringUtils.isEmpty(responseMsg.getError())) { | 143 | if (!StringUtils.isEmpty(responseMsg.getError())) { |
144 | throw new AdaptorException(responseMsg.getError()); | 144 | throw new AdaptorException(responseMsg.getError()); |
145 | } else { | 145 | } else { |
146 | - JsonObject result = JsonConverter.getJsonObjectForGateway(responseMsg); | 146 | + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg); |
147 | return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result)); | 147 | return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result)); |
148 | } | 148 | } |
149 | } | 149 | } |
@@ -48,7 +48,7 @@ public interface MqttTransportAdaptor { | @@ -48,7 +48,7 @@ public interface MqttTransportAdaptor { | ||
48 | 48 | ||
49 | Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException; | 49 | Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException; |
50 | 50 | ||
51 | - Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, GetAttributeResponseMsg responseMsg) throws AdaptorException; | 51 | + Optional<MqttMessage> convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, GetAttributeResponseMsg responseMsg) throws AdaptorException; |
52 | 52 | ||
53 | Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; | 53 | Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; |
54 | 54 |
@@ -65,7 +65,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | @@ -65,7 +65,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | ||
65 | @Override | 65 | @Override |
66 | public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { | 66 | public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) { |
67 | try { | 67 | try { |
68 | - parent.getAdaptor().convertToGatewayPublish(this, response).ifPresent(parent::writeAndFlush); | 68 | + parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush); |
69 | } catch (Exception e) { | 69 | } catch (Exception e) { |
70 | log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); | 70 | log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); |
71 | } | 71 | } |
@@ -279,10 +279,10 @@ public class GatewaySessionHandler { | @@ -279,10 +279,10 @@ public class GatewaySessionHandler { | ||
279 | 279 | ||
280 | @Override | 280 | @Override |
281 | public void onFailure(Throwable t) { | 281 | public void onFailure(Throwable t) { |
282 | + ack(msg); | ||
282 | log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t); | 283 | log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t); |
283 | } | 284 | } |
284 | }, context.getExecutor()); | 285 | }, context.getExecutor()); |
285 | - ack(msg); | ||
286 | } else { | 286 | } else { |
287 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); | 287 | throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); |
288 | } | 288 | } |
@@ -257,9 +257,10 @@ public class JsonConverter { | @@ -257,9 +257,10 @@ public class JsonConverter { | ||
257 | return result; | 257 | return result; |
258 | } | 258 | } |
259 | 259 | ||
260 | - public static JsonObject getJsonObjectForGateway(TransportProtos.GetAttributeResponseMsg responseMsg) { | 260 | + public static JsonObject getJsonObjectForGateway(String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) { |
261 | JsonObject result = new JsonObject(); | 261 | JsonObject result = new JsonObject(); |
262 | result.addProperty("id", responseMsg.getRequestId()); | 262 | result.addProperty("id", responseMsg.getRequestId()); |
263 | + result.addProperty(DEVICE_PROPERTY, deviceName); | ||
263 | if (responseMsg.getClientAttributeListCount() > 0) { | 264 | if (responseMsg.getClientAttributeListCount() > 0) { |
264 | addValues(result, responseMsg.getClientAttributeListList()); | 265 | addValues(result, responseMsg.getClientAttributeListList()); |
265 | } | 266 | } |
@@ -18,11 +18,7 @@ package org.thingsboard.server.dao; | @@ -18,11 +18,7 @@ package org.thingsboard.server.dao; | ||
18 | import org.thingsboard.server.common.data.id.UUIDBased; | 18 | import org.thingsboard.server.common.data.id.UUIDBased; |
19 | import org.thingsboard.server.dao.model.ToData; | 19 | import org.thingsboard.server.dao.model.ToData; |
20 | 20 | ||
21 | -import java.util.ArrayList; | ||
22 | -import java.util.Collection; | ||
23 | -import java.util.Collections; | ||
24 | -import java.util.List; | ||
25 | -import java.util.UUID; | 21 | +import java.util.*; |
26 | 22 | ||
27 | public abstract class DaoUtil { | 23 | public abstract class DaoUtil { |
28 | 24 | ||
@@ -50,6 +46,14 @@ public abstract class DaoUtil { | @@ -50,6 +46,14 @@ public abstract class DaoUtil { | ||
50 | return object; | 46 | return object; |
51 | } | 47 | } |
52 | 48 | ||
49 | + public static <T> T getData(Optional<? extends ToData<T>> data) { | ||
50 | + T object = null; | ||
51 | + if (data.isPresent()) { | ||
52 | + object = data.get().toData(); | ||
53 | + } | ||
54 | + return object; | ||
55 | + } | ||
56 | + | ||
53 | public static UUID getId(UUIDBased idBased) { | 57 | public static UUID getId(UUIDBased idBased) { |
54 | UUID id = null; | 58 | UUID id = null; |
55 | if (idBased != null) { | 59 | if (idBased != null) { |
@@ -60,7 +60,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; | @@ -60,7 +60,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; | ||
60 | 60 | ||
61 | @Slf4j | 61 | @Slf4j |
62 | @Service | 62 | @Service |
63 | -@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true") | 63 | +@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true") |
64 | public class AuditLogServiceImpl implements AuditLogService { | 64 | public class AuditLogServiceImpl implements AuditLogService { |
65 | 65 | ||
66 | private static final ObjectMapper objectMapper = new ObjectMapper(); | 66 | private static final ObjectMapper objectMapper = new ObjectMapper(); |
@@ -88,11 +88,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | @@ -88,11 +88,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo | ||
88 | 88 | ||
89 | protected ExecutorService readResultsProcessingExecutor; | 89 | protected ExecutorService readResultsProcessingExecutor; |
90 | 90 | ||
91 | - @Value("${audit_log.by_tenant_partitioning}") | 91 | + @Value("${audit-log.by_tenant_partitioning}") |
92 | private String partitioning; | 92 | private String partitioning; |
93 | private TsPartitionDate tsFormat; | 93 | private TsPartitionDate tsFormat; |
94 | 94 | ||
95 | - @Value("${audit_log.default_query_period}") | 95 | + @Value("${audit-log.default_query_period}") |
96 | private Integer defaultQueryPeriodInDays; | 96 | private Integer defaultQueryPeriodInDays; |
97 | 97 | ||
98 | private PreparedStatement partitionInsertStmt; | 98 | private PreparedStatement partitionInsertStmt; |
@@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; | @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; | ||
33 | import java.util.List; | 33 | import java.util.List; |
34 | 34 | ||
35 | @Service | 35 | @Service |
36 | -@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false") | 36 | +@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "false") |
37 | public class DummyAuditLogServiceImpl implements AuditLogService { | 37 | public class DummyAuditLogServiceImpl implements AuditLogService { |
38 | 38 | ||
39 | @Override | 39 | @Override |
@@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; | @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component; | ||
20 | import org.thingsboard.server.common.data.audit.AuditLog; | 20 | import org.thingsboard.server.common.data.audit.AuditLog; |
21 | 21 | ||
22 | @Component | 22 | @Component |
23 | -@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "none") | 23 | +@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "none") |
24 | public class DummyAuditLogSink implements AuditLogSink { | 24 | public class DummyAuditLogSink implements AuditLogSink { |
25 | 25 | ||
26 | @Override | 26 | @Override |
@@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter; | @@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter; | ||
44 | import java.util.Collections; | 44 | import java.util.Collections; |
45 | 45 | ||
46 | @Component | 46 | @Component |
47 | -@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "elasticsearch") | 47 | +@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "elasticsearch") |
48 | @Slf4j | 48 | @Slf4j |
49 | public class ElasticsearchAuditLogSink implements AuditLogSink { | 49 | public class ElasticsearchAuditLogSink implements AuditLogSink { |
50 | 50 | ||
@@ -54,19 +54,19 @@ public class ElasticsearchAuditLogSink implements AuditLogSink { | @@ -54,19 +54,19 @@ public class ElasticsearchAuditLogSink implements AuditLogSink { | ||
54 | 54 | ||
55 | private final ObjectMapper mapper = new ObjectMapper(); | 55 | private final ObjectMapper mapper = new ObjectMapper(); |
56 | 56 | ||
57 | - @Value("${audit_log.sink.index_pattern}") | 57 | + @Value("${audit-log.sink.index_pattern}") |
58 | private String indexPattern; | 58 | private String indexPattern; |
59 | - @Value("${audit_log.sink.scheme_name}") | 59 | + @Value("${audit-log.sink.scheme_name}") |
60 | private String schemeName; | 60 | private String schemeName; |
61 | - @Value("${audit_log.sink.host}") | 61 | + @Value("${audit-log.sink.host}") |
62 | private String host; | 62 | private String host; |
63 | - @Value("${audit_log.sink.port}") | 63 | + @Value("${audit-log.sink.port}") |
64 | private int port; | 64 | private int port; |
65 | - @Value("${audit_log.sink.user_name}") | 65 | + @Value("${audit-log.sink.user_name}") |
66 | private String userName; | 66 | private String userName; |
67 | - @Value("${audit_log.sink.password}") | 67 | + @Value("${audit-log.sink.password}") |
68 | private String password; | 68 | private String password; |
69 | - @Value("${audit_log.sink.date_format}") | 69 | + @Value("${audit-log.sink.date_format}") |
70 | private String dateFormat; | 70 | private String dateFormat; |
71 | 71 | ||
72 | private RestClient restClient; | 72 | private RestClient restClient; |
@@ -19,14 +19,37 @@ import lombok.Data; | @@ -19,14 +19,37 @@ import lombok.Data; | ||
19 | import org.springframework.beans.factory.annotation.Value; | 19 | import org.springframework.beans.factory.annotation.Value; |
20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | 20 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
21 | import org.springframework.cache.CacheManager; | 21 | import org.springframework.cache.CacheManager; |
22 | +import org.springframework.cache.interceptor.SimpleKey; | ||
23 | +import org.springframework.core.convert.ConversionService; | ||
24 | +import org.springframework.core.convert.TypeDescriptor; | ||
25 | +import org.springframework.core.convert.converter.ConditionalGenericConverter; | ||
26 | +import org.springframework.core.convert.converter.Converter; | ||
22 | import org.springframework.cache.annotation.EnableCaching; | 27 | import org.springframework.cache.annotation.EnableCaching; |
23 | import org.springframework.cache.interceptor.KeyGenerator; | 28 | import org.springframework.cache.interceptor.KeyGenerator; |
24 | import org.springframework.context.annotation.Bean; | 29 | import org.springframework.context.annotation.Bean; |
25 | import org.springframework.context.annotation.Configuration; | 30 | import org.springframework.context.annotation.Configuration; |
31 | +import org.springframework.core.convert.converter.ConverterRegistry; | ||
32 | +import org.springframework.data.convert.ReadingConverter; | ||
33 | +import org.springframework.data.convert.WritingConverter; | ||
34 | +import org.springframework.data.redis.cache.RedisCacheConfiguration; | ||
26 | import org.springframework.data.redis.cache.RedisCacheManager; | 35 | import org.springframework.data.redis.cache.RedisCacheManager; |
27 | import org.springframework.data.redis.connection.RedisConnectionFactory; | 36 | import org.springframework.data.redis.connection.RedisConnectionFactory; |
28 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; | 37 | import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; |
29 | import org.springframework.data.redis.core.RedisTemplate; | 38 | import org.springframework.data.redis.core.RedisTemplate; |
39 | +import org.springframework.data.redis.core.convert.RedisCustomConversions; | ||
40 | +import org.springframework.format.support.DefaultFormattingConversionService; | ||
41 | +import org.springframework.lang.Nullable; | ||
42 | +import org.springframework.util.Assert; | ||
43 | +import org.springframework.util.StringUtils; | ||
44 | +import org.thingsboard.server.common.data.EntityType; | ||
45 | +import org.thingsboard.server.common.data.id.EntityId; | ||
46 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | ||
47 | + | ||
48 | +import java.nio.charset.StandardCharsets; | ||
49 | +import java.util.Arrays; | ||
50 | +import java.util.Collections; | ||
51 | +import java.util.Set; | ||
52 | +import java.util.UUID; | ||
30 | 53 | ||
31 | @Configuration | 54 | @Configuration |
32 | @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false) | 55 | @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false) |
@@ -57,15 +80,12 @@ public class TBRedisCacheConfiguration { | @@ -57,15 +80,12 @@ public class TBRedisCacheConfiguration { | ||
57 | } | 80 | } |
58 | 81 | ||
59 | @Bean | 82 | @Bean |
60 | - public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) { | ||
61 | - RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); | ||
62 | - redisTemplate.setConnectionFactory(cf); | ||
63 | - return redisTemplate; | ||
64 | - } | ||
65 | - | ||
66 | - @Bean | ||
67 | - public CacheManager cacheManager(RedisTemplate redisTemplate) { | ||
68 | - return new RedisCacheManager(redisTemplate); | 83 | + public CacheManager cacheManager(RedisConnectionFactory cf) { |
84 | + DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService(); | ||
85 | + RedisCacheConfiguration.registerDefaultConverters(redisConversionService); | ||
86 | + registerDefaultConverters(redisConversionService); | ||
87 | + RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService); | ||
88 | + return RedisCacheManager.builder(cf).cacheDefaults(configuration).build(); | ||
69 | } | 89 | } |
70 | 90 | ||
71 | @Bean | 91 | @Bean |
@@ -73,5 +93,8 @@ public class TBRedisCacheConfiguration { | @@ -73,5 +93,8 @@ public class TBRedisCacheConfiguration { | ||
73 | return new PreviousDeviceCredentialsIdKeyGenerator(); | 93 | return new PreviousDeviceCredentialsIdKeyGenerator(); |
74 | } | 94 | } |
75 | 95 | ||
76 | - | 96 | + private static void registerDefaultConverters(ConverterRegistry registry) { |
97 | + Assert.notNull(registry, "ConverterRegistry must not be null!"); | ||
98 | + registry.addConverter(EntityId.class, String.class, EntityId::toString); | ||
99 | + } | ||
77 | } | 100 | } |
@@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao; | @@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao; | ||
21 | 21 | ||
22 | import javax.annotation.PostConstruct; | 22 | import javax.annotation.PostConstruct; |
23 | 23 | ||
24 | -@Component | 24 | +@Component("CassandraCluster") |
25 | @NoSqlAnyDao | 25 | @NoSqlAnyDao |
26 | public class CassandraCluster extends AbstractCassandraCluster { | 26 | public class CassandraCluster extends AbstractCassandraCluster { |
27 | 27 |
@@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao; | @@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao; | ||
21 | 21 | ||
22 | import javax.annotation.PostConstruct; | 22 | import javax.annotation.PostConstruct; |
23 | 23 | ||
24 | -@Component | 24 | +@Component("CassandraInstallCluster") |
25 | @NoSqlAnyDao | 25 | @NoSqlAnyDao |
26 | @Profile("install") | 26 | @Profile("install") |
27 | public class CassandraInstallCluster extends AbstractCassandraCluster { | 27 | public class CassandraInstallCluster extends AbstractCassandraCluster { |
@@ -140,7 +140,7 @@ public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntit | @@ -140,7 +140,7 @@ public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntit | ||
140 | auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); | 140 | auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString())); |
141 | } | 141 | } |
142 | if (userId != null) { | 142 | if (userId != null) { |
143 | - auditLog.setUserId(new UserId(toUUID(entityId))); | 143 | + auditLog.setUserId(new UserId(toUUID(userId))); |
144 | } | 144 | } |
145 | auditLog.setEntityName(this.entityName); | 145 | auditLog.setEntityName(this.entityName); |
146 | auditLog.setUserName(this.userName); | 146 | auditLog.setUserName(this.userName); |
@@ -27,6 +27,7 @@ import com.datastax.driver.core.TypeCodec; | @@ -27,6 +27,7 @@ import com.datastax.driver.core.TypeCodec; | ||
27 | import com.datastax.driver.core.exceptions.CodecNotFoundException; | 27 | import com.datastax.driver.core.exceptions.CodecNotFoundException; |
28 | import lombok.extern.slf4j.Slf4j; | 28 | import lombok.extern.slf4j.Slf4j; |
29 | import org.springframework.beans.factory.annotation.Autowired; | 29 | import org.springframework.beans.factory.annotation.Autowired; |
30 | +import org.springframework.beans.factory.annotation.Qualifier; | ||
30 | import org.thingsboard.server.common.data.id.TenantId; | 31 | import org.thingsboard.server.common.data.id.TenantId; |
31 | import org.thingsboard.server.dao.cassandra.CassandraCluster; | 32 | import org.thingsboard.server.dao.cassandra.CassandraCluster; |
32 | import org.thingsboard.server.dao.model.type.AuthorityCodec; | 33 | import org.thingsboard.server.dao.model.type.AuthorityCodec; |
@@ -44,6 +45,7 @@ import java.util.concurrent.ConcurrentMap; | @@ -44,6 +45,7 @@ import java.util.concurrent.ConcurrentMap; | ||
44 | public abstract class CassandraAbstractDao { | 45 | public abstract class CassandraAbstractDao { |
45 | 46 | ||
46 | @Autowired | 47 | @Autowired |
48 | + @Qualifier("CassandraCluster") | ||
47 | protected CassandraCluster cluster; | 49 | protected CassandraCluster cluster; |
48 | 50 | ||
49 | private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); | 51 | private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); |
@@ -27,6 +27,7 @@ import org.thingsboard.server.dao.DaoUtil; | @@ -27,6 +27,7 @@ import org.thingsboard.server.dao.DaoUtil; | ||
27 | import org.thingsboard.server.dao.model.BaseEntity; | 27 | import org.thingsboard.server.dao.model.BaseEntity; |
28 | 28 | ||
29 | import java.util.List; | 29 | import java.util.List; |
30 | +import java.util.Optional; | ||
30 | import java.util.UUID; | 31 | import java.util.UUID; |
31 | 32 | ||
32 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; | 33 | import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID; |
@@ -67,23 +68,23 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D> | @@ -67,23 +68,23 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D> | ||
67 | @Override | 68 | @Override |
68 | public D findById(TenantId tenantId, UUID key) { | 69 | public D findById(TenantId tenantId, UUID key) { |
69 | log.debug("Get entity by key {}", key); | 70 | log.debug("Get entity by key {}", key); |
70 | - E entity = getCrudRepository().findOne(fromTimeUUID(key)); | 71 | + Optional<E> entity = getCrudRepository().findById(fromTimeUUID(key)); |
71 | return DaoUtil.getData(entity); | 72 | return DaoUtil.getData(entity); |
72 | } | 73 | } |
73 | 74 | ||
74 | @Override | 75 | @Override |
75 | public ListenableFuture<D> findByIdAsync(TenantId tenantId, UUID key) { | 76 | public ListenableFuture<D> findByIdAsync(TenantId tenantId, UUID key) { |
76 | log.debug("Get entity by key async {}", key); | 77 | log.debug("Get entity by key async {}", key); |
77 | - return service.submit(() -> DaoUtil.getData(getCrudRepository().findOne(fromTimeUUID(key)))); | 78 | + return service.submit(() -> DaoUtil.getData(getCrudRepository().findById(fromTimeUUID(key)))); |
78 | } | 79 | } |
79 | 80 | ||
80 | @Override | 81 | @Override |
81 | @Transactional | 82 | @Transactional |
82 | public boolean removeById(TenantId tenantId, UUID id) { | 83 | public boolean removeById(TenantId tenantId, UUID id) { |
83 | String key = fromTimeUUID(id); | 84 | String key = fromTimeUUID(id); |
84 | - getCrudRepository().delete(key); | 85 | + getCrudRepository().deleteById(key); |
85 | log.debug("Remove request: {}", key); | 86 | log.debug("Remove request: {}", key); |
86 | - return getCrudRepository().findOne(key) == null; | 87 | + return !getCrudRepository().existsById(key); |
87 | } | 88 | } |
88 | 89 | ||
89 | @Override | 90 | @Override |
@@ -52,7 +52,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | @@ -52,7 +52,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | ||
52 | AttributeKvCompositeKey compositeKey = | 52 | AttributeKvCompositeKey compositeKey = |
53 | getAttributeKvCompositeKey(entityId, attributeType, attributeKey); | 53 | getAttributeKvCompositeKey(entityId, attributeType, attributeKey); |
54 | return Futures.immediateFuture( | 54 | return Futures.immediateFuture( |
55 | - Optional.ofNullable(DaoUtil.getData(attributeKvRepository.findOne(compositeKey)))); | 55 | + Optional.ofNullable(DaoUtil.getData(attributeKvRepository.findById(compositeKey)))); |
56 | } | 56 | } |
57 | 57 | ||
58 | @Override | 58 | @Override |
@@ -64,7 +64,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | @@ -64,7 +64,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | ||
64 | getAttributeKvCompositeKey(entityId, attributeType, attributeKey)) | 64 | getAttributeKvCompositeKey(entityId, attributeType, attributeKey)) |
65 | .collect(Collectors.toList()); | 65 | .collect(Collectors.toList()); |
66 | return Futures.immediateFuture( | 66 | return Futures.immediateFuture( |
67 | - DaoUtil.convertDataList(Lists.newArrayList(attributeKvRepository.findAll(compositeKeys)))); | 67 | + DaoUtil.convertDataList(Lists.newArrayList(attributeKvRepository.findAllById(compositeKeys)))); |
68 | } | 68 | } |
69 | 69 | ||
70 | @Override | 70 | @Override |
@@ -103,7 +103,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | @@ -103,7 +103,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl | ||
103 | }).collect(Collectors.toList()); | 103 | }).collect(Collectors.toList()); |
104 | 104 | ||
105 | return service.submit(() -> { | 105 | return service.submit(() -> { |
106 | - attributeKvRepository.delete(entitiesToDelete); | 106 | + attributeKvRepository.deleteAll(entitiesToDelete); |
107 | return null; | 107 | return null; |
108 | }); | 108 | }); |
109 | } | 109 | } |
@@ -66,7 +66,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao<Comp | @@ -66,7 +66,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao<Comp | ||
66 | if (component.getId() == null) { | 66 | if (component.getId() == null) { |
67 | component.setId(new ComponentDescriptorId(UUIDs.timeBased())); | 67 | component.setId(new ComponentDescriptorId(UUIDs.timeBased())); |
68 | } | 68 | } |
69 | - if (componentDescriptorRepository.findOne(UUIDConverter.fromTimeUUID(component.getId().getId())) == null) { | 69 | + if (!componentDescriptorRepository.existsById(UUIDConverter.fromTimeUUID(component.getId().getId()))) { |
70 | return Optional.of(save(tenantId, component)); | 70 | return Optional.of(save(tenantId, component)); |
71 | } | 71 | } |
72 | return Optional.empty(); | 72 | return Optional.empty(); |
@@ -97,13 +97,13 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple | @@ -97,13 +97,13 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple | ||
97 | @Override | 97 | @Override |
98 | public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { | 98 | public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { |
99 | RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup); | 99 | RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup); |
100 | - return service.submit(() -> relationRepository.findOne(key) != null); | 100 | + return service.submit(() -> relationRepository.existsById(key)); |
101 | } | 101 | } |
102 | 102 | ||
103 | @Override | 103 | @Override |
104 | public ListenableFuture<EntityRelation> getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { | 104 | public ListenableFuture<EntityRelation> getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { |
105 | RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup); | 105 | RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup); |
106 | - return service.submit(() -> DaoUtil.getData(relationRepository.findOne(key))); | 106 | + return service.submit(() -> DaoUtil.getData(relationRepository.findById(key))); |
107 | } | 107 | } |
108 | 108 | ||
109 | private RelationCompositeKey getRelationCompositeKey(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { | 109 | private RelationCompositeKey getRelationCompositeKey(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { |
@@ -152,9 +152,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple | @@ -152,9 +152,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple | ||
152 | } | 152 | } |
153 | 153 | ||
154 | private boolean deleteRelationIfExists(RelationCompositeKey key) { | 154 | private boolean deleteRelationIfExists(RelationCompositeKey key) { |
155 | - boolean relationExistsBeforeDelete = relationRepository.exists(key); | 155 | + boolean relationExistsBeforeDelete = relationRepository.existsById(key); |
156 | if (relationExistsBeforeDelete) { | 156 | if (relationExistsBeforeDelete) { |
157 | - relationRepository.delete(key); | 157 | + relationRepository.deleteById(key); |
158 | } | 158 | } |
159 | return relationExistsBeforeDelete; | 159 | return relationExistsBeforeDelete; |
160 | } | 160 | } |
@@ -53,7 +53,7 @@ public interface RelationRepository | @@ -53,7 +53,7 @@ public interface RelationRepository | ||
53 | RelationEntity save(RelationEntity entity); | 53 | RelationEntity save(RelationEntity entity); |
54 | 54 | ||
55 | @Transactional | 55 | @Transactional |
56 | - void delete(RelationCompositeKey id); | 56 | + void deleteById(RelationCompositeKey id); |
57 | 57 | ||
58 | @Transactional | 58 | @Transactional |
59 | void deleteByFromIdAndFromType(String fromId, String fromType); | 59 | void deleteByFromIdAndFromType(String fromId, String fromType); |
@@ -284,10 +284,10 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | @@ -284,10 +284,10 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp | ||
284 | entityId.getEntityType(), | 284 | entityId.getEntityType(), |
285 | fromTimeUUID(entityId.getId()), | 285 | fromTimeUUID(entityId.getId()), |
286 | key); | 286 | key); |
287 | - TsKvLatestEntity entry = tsKvLatestRepository.findOne(compositeKey); | 287 | + Optional<TsKvLatestEntity> entry = tsKvLatestRepository.findById(compositeKey); |
288 | TsKvEntry result; | 288 | TsKvEntry result; |
289 | - if (entry != null) { | ||
290 | - result = DaoUtil.getData(entry); | 289 | + if (entry.isPresent()) { |
290 | + result = DaoUtil.getData(entry.get()); | ||
291 | } else { | 291 | } else { |
292 | result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); | 292 | result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null)); |
293 | } | 293 | } |
@@ -82,6 +82,7 @@ public class CustomCassandraCQLUnit extends BaseCassandraUnit { | @@ -82,6 +82,7 @@ public class CustomCassandraCQLUnit extends BaseCassandraUnit { | ||
82 | session = null; | 82 | session = null; |
83 | cluster = null; | 83 | cluster = null; |
84 | } | 84 | } |
85 | + System.setSecurityManager(null); | ||
85 | } | 86 | } |
86 | 87 | ||
87 | // Getters for those who do not like to directly access fields | 88 | // Getters for those who do not like to directly access fields |
@@ -4,10 +4,10 @@ zk.zk_dir=/thingsboard | @@ -4,10 +4,10 @@ zk.zk_dir=/thingsboard | ||
4 | 4 | ||
5 | updates.enabled=false | 5 | updates.enabled=false |
6 | 6 | ||
7 | -audit_log.enabled=true | ||
8 | -audit_log.by_tenant_partitioning=MONTHS | ||
9 | -audit_log.default_query_period=30 | ||
10 | -audit_log.sink.type=none | 7 | +audit-log.enabled=true |
8 | +audit-log.by_tenant_partitioning=MONTHS | ||
9 | +audit-log.default_query_period=30 | ||
10 | +audit-log.sink.type=none | ||
11 | 11 | ||
12 | cache.type=caffeine | 12 | cache.type=caffeine |
13 | #cache.type=redis | 13 | #cache.type=redis |
@@ -4,6 +4,7 @@ database.entities.type=sql | @@ -4,6 +4,7 @@ database.entities.type=sql | ||
4 | sql.ts_inserts_executor_type=fixed | 4 | sql.ts_inserts_executor_type=fixed |
5 | sql.ts_inserts_fixed_thread_pool_size=10 | 5 | sql.ts_inserts_fixed_thread_pool_size=10 |
6 | 6 | ||
7 | +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true | ||
7 | spring.jpa.show-sql=false | 8 | spring.jpa.show-sql=false |
8 | spring.jpa.hibernate.ddl-auto=validate | 9 | spring.jpa.hibernate.ddl-auto=validate |
9 | spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect | 10 | spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect |
k8s/.env
0 → 100644
k8s/README.md
0 → 100644
1 | +# Kubernetes resources configuration for ThingsBoard Microservices | ||
2 | + | ||
3 | +This folder containing scripts and Kubernetes resources configurations to run ThingsBoard in Microservices mode. | ||
4 | + | ||
5 | +## Prerequisites | ||
6 | + | ||
7 | +ThingsBoard Microservices are running on Kubernetes cluster. | ||
8 | +You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster. | ||
9 | +If you do not already have a cluster, you can create one by using [Minikube](https://kubernetes.io/docs/setup/minikube), | ||
10 | +or you can choose any other available [Kubernetes cluster deployment solutions](https://kubernetes.io/docs/setup/pick-right-solution/). | ||
11 | + | ||
12 | +## Installation | ||
13 | + | ||
14 | +Before performing initial installation you can configure the type of database to be used with ThingsBoard. | ||
15 | +In order to set database type change the value of `DATABASE` variable in `.env` file to one of the following: | ||
16 | + | ||
17 | +- `postgres` - use PostgreSQL database; | ||
18 | +- `cassandra` - use Cassandra database; | ||
19 | + | ||
20 | +**NOTE**: According to the database type corresponding kubernetes resources will be deployed (see `postgres.yml`, `cassandra.yml` for details). | ||
21 | + | ||
22 | +Execute the following command to run installation: | ||
23 | + | ||
24 | +` | ||
25 | +$ ./k8s-install-tb.sh --loadDemo | ||
26 | +` | ||
27 | + | ||
28 | +Where: | ||
29 | + | ||
30 | +- `--loadDemo` - optional argument. Whether to load additional demo data. | ||
31 | + | ||
32 | +## Running | ||
33 | + | ||
34 | +Execute the following command to deploy resources: | ||
35 | + | ||
36 | +` | ||
37 | +$ ./k8s-deploy-resources.sh | ||
38 | +` | ||
39 | + | ||
40 | +After a while when all resources will be successfully started you can open `http://{your-cluster-ip}` in you browser (for ex. `http://192.168.99.101`). | ||
41 | +You should see ThingsBoard login page. | ||
42 | + | ||
43 | +Use the following default credentials: | ||
44 | + | ||
45 | +- **System Administrator**: sysadmin@thingsboard.org / sysadmin | ||
46 | + | ||
47 | +If you installed DataBase with demo data (using `--loadDemo` flag) you can also use the following credentials: | ||
48 | + | ||
49 | +- **Tenant Administrator**: tenant@thingsboard.org / tenant | ||
50 | +- **Customer User**: customer@thingsboard.org / customer | ||
51 | + | ||
52 | +In case of any issues you can examine service logs for errors. | ||
53 | +For example to see ThingsBoard node logs execute the following commands: | ||
54 | + | ||
55 | +1) Get list of the running tb-node pods: | ||
56 | + | ||
57 | +` | ||
58 | +$ kubectl get pods -l app=tb-node | ||
59 | +` | ||
60 | + | ||
61 | +2) Fetch logs of tb-node pod: | ||
62 | + | ||
63 | +` | ||
64 | +$ kubectl logs -f [tb-node-pod-name] | ||
65 | +` | ||
66 | + | ||
67 | +Where: | ||
68 | + | ||
69 | +- `tb-node-pod-name` - tb-node pod name obtained from the list of the running tb-node pods. | ||
70 | + | ||
71 | +Or use `kubectl get pods` to see the state of all the pods. | ||
72 | +Or use `kubectl get services` to see the state of all the services. | ||
73 | +Or use `kubectl get deployments` to see the state of all the deployments. | ||
74 | +See [kubectl Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/) command reference for details. | ||
75 | + | ||
76 | +Execute the following command to delete all deployed microservices: | ||
77 | + | ||
78 | +` | ||
79 | +$ ./k8s-delete-resources.sh | ||
80 | +` | ||
81 | + | ||
82 | +Execute the following command to delete all resources (including database): | ||
83 | + | ||
84 | +` | ||
85 | +$ ./k8s-delete-all.sh | ||
86 | +` | ||
87 | + | ||
88 | +## Upgrading | ||
89 | + | ||
90 | +In case when database upgrade is needed, execute the following commands: | ||
91 | + | ||
92 | +``` | ||
93 | +$ ./k8s-delete-resources.sh | ||
94 | +$ ./k8s-upgrade-tb.sh --fromVersion=[FROM_VERSION] | ||
95 | +$ ./k8s-deploy-resources.sh | ||
96 | +``` | ||
97 | + | ||
98 | +Where: | ||
99 | + | ||
100 | +- `FROM_VERSION` - from which version upgrade should be started. See [Upgrade Instructions](https://thingsboard.io/docs/user-guide/install/upgrade-instructions) for valid `fromVersion` values. |
k8s/cassandra.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: storage.k8s.io/v1 | ||
18 | +kind: StorageClass | ||
19 | +metadata: | ||
20 | + name: fast | ||
21 | + namespace: thingsboard | ||
22 | +provisioner: k8s.io/minikube-hostpath | ||
23 | +parameters: | ||
24 | + type: pd-ssd | ||
25 | +--- | ||
26 | +apiVersion: v1 | ||
27 | +kind: ConfigMap | ||
28 | +metadata: | ||
29 | + name: cassandra-probe-config | ||
30 | + namespace: thingsboard | ||
31 | + labels: | ||
32 | + name: cassandra-probe-config | ||
33 | +data: | ||
34 | + probe: | | ||
35 | + if [[ $(nodetool status | grep $POD_IP) == *"UN"* ]]; then | ||
36 | + if [[ $DEBUG ]]; then | ||
37 | + echo "UN"; | ||
38 | + fi | ||
39 | + exit 0; | ||
40 | + else | ||
41 | + if [[ $DEBUG ]]; then | ||
42 | + echo "Not Up"; | ||
43 | + fi | ||
44 | + exit 1; | ||
45 | + fi | ||
46 | +--- | ||
47 | +apiVersion: apps/v1 | ||
48 | +kind: StatefulSet | ||
49 | +metadata: | ||
50 | + name: cassandra | ||
51 | + namespace: thingsboard | ||
52 | + labels: | ||
53 | + app: cassandra | ||
54 | +spec: | ||
55 | + serviceName: cassandra | ||
56 | + replicas: 1 | ||
57 | + selector: | ||
58 | + matchLabels: | ||
59 | + app: cassandra | ||
60 | + template: | ||
61 | + metadata: | ||
62 | + labels: | ||
63 | + app: cassandra | ||
64 | + spec: | ||
65 | + volumes: | ||
66 | + - name: cassandra-probe-config | ||
67 | + configMap: | ||
68 | + name: cassandra-probe-config | ||
69 | + items: | ||
70 | + - key: probe | ||
71 | + path: ready-probe.sh | ||
72 | + mode: 0777 | ||
73 | + terminationGracePeriodSeconds: 1800 | ||
74 | + containers: | ||
75 | + - name: cassandra | ||
76 | + image: cassandra:3.11.3 | ||
77 | + imagePullPolicy: Always | ||
78 | + ports: | ||
79 | + - containerPort: 7000 | ||
80 | + name: intra-node | ||
81 | + - containerPort: 7001 | ||
82 | + name: tls-intra-node | ||
83 | + - containerPort: 7199 | ||
84 | + name: jmx | ||
85 | + - containerPort: 9042 | ||
86 | + name: cql | ||
87 | + - containerPort: 9160 | ||
88 | + name: thrift | ||
89 | + resources: | ||
90 | + limits: | ||
91 | + cpu: "1000m" | ||
92 | + memory: 2Gi | ||
93 | + requests: | ||
94 | + cpu: "1000m" | ||
95 | + memory: 2Gi | ||
96 | + securityContext: | ||
97 | + capabilities: | ||
98 | + add: | ||
99 | + - IPC_LOCK | ||
100 | + lifecycle: | ||
101 | + preStop: | ||
102 | + exec: | ||
103 | + command: | ||
104 | + - /bin/sh | ||
105 | + - -c | ||
106 | + - nodetool drain | ||
107 | + env: | ||
108 | + - name: CASSANDRA_SEEDS | ||
109 | + value: "cassandra-0.cassandra.thingsboard.svc.cluster.local" | ||
110 | + - name: MAX_HEAP_SIZE | ||
111 | + value: 1024M | ||
112 | + - name: HEAP_NEWSIZE | ||
113 | + value: 256M | ||
114 | + - name: CASSANDRA_CLUSTER_NAME | ||
115 | + value: "Thingsboard Cluster" | ||
116 | + - name: CASSANDRA_DC | ||
117 | + value: "DC1-Thingsboard-Cluster" | ||
118 | + - name: CASSANDRA_RACK | ||
119 | + value: "Rack-Thingsboard-Cluster" | ||
120 | + - name: CASSANDRA_AUTO_BOOTSTRAP | ||
121 | + value: "false" | ||
122 | + - name: CASSANDRA_ENDPOINT_SNITCH | ||
123 | + value: GossipingPropertyFileSnitch | ||
124 | + - name: POD_IP | ||
125 | + valueFrom: | ||
126 | + fieldRef: | ||
127 | + fieldPath: status.podIP | ||
128 | + readinessProbe: | ||
129 | + exec: | ||
130 | + command: | ||
131 | + - /bin/bash | ||
132 | + - -c | ||
133 | + - /probe/ready-probe.sh | ||
134 | + initialDelaySeconds: 60 | ||
135 | + timeoutSeconds: 5 | ||
136 | + volumeMounts: | ||
137 | + - name: cassandra-probe-config | ||
138 | + mountPath: /probe | ||
139 | + - name: cassandra-data | ||
140 | + mountPath: /var/lib/cassandra | ||
141 | + volumeClaimTemplates: | ||
142 | + - metadata: | ||
143 | + name: cassandra-data | ||
144 | + spec: | ||
145 | + accessModes: [ "ReadWriteOnce" ] | ||
146 | + storageClassName: fast | ||
147 | + resources: | ||
148 | + requests: | ||
149 | + storage: 1Gi | ||
150 | +--- | ||
151 | +apiVersion: v1 | ||
152 | +kind: Service | ||
153 | +metadata: | ||
154 | + labels: | ||
155 | + app: cassandra | ||
156 | + name: cassandra | ||
157 | + namespace: thingsboard | ||
158 | +spec: | ||
159 | + clusterIP: None | ||
160 | + ports: | ||
161 | + - port: 9042 | ||
162 | + selector: | ||
163 | + app: cassandra | ||
164 | +--- |
k8s/database-setup.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: Pod | ||
19 | +metadata: | ||
20 | + name: tb-db-setup | ||
21 | + namespace: thingsboard | ||
22 | +spec: | ||
23 | + volumes: | ||
24 | + - name: tb-node-config | ||
25 | + configMap: | ||
26 | + name: tb-node-config | ||
27 | + items: | ||
28 | + - key: conf | ||
29 | + path: thingsboard.conf | ||
30 | + - key: logback | ||
31 | + path: logback.xml | ||
32 | + containers: | ||
33 | + - name: tb-db-setup | ||
34 | + imagePullPolicy: Always | ||
35 | + image: thingsboard/tb-node:latest | ||
36 | + envFrom: | ||
37 | + - configMapRef: | ||
38 | + name: tb-node-db-config | ||
39 | + volumeMounts: | ||
40 | + - mountPath: /config | ||
41 | + name: tb-node-config | ||
42 | + command: ['sh', '-c', 'while [ ! -f /install-finished ]; do sleep 2; done;'] | ||
43 | + restartPolicy: Never |
k8s/k8s-delete-all.sh
0 → 100755
1 | +#!/bin/bash | ||
2 | +# | ||
3 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +kubectl -n thingsboard delete svc,sts,deploy,pv,pvc,cm,po,ing --all --include-uninitialized |
k8s/k8s-delete-resources.sh
0 → 100755
1 | +#!/bin/bash | ||
2 | +# | ||
3 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +set -e | ||
19 | + | ||
20 | +kubectl config set-context $(kubectl config current-context) --namespace=thingsboard | ||
21 | +kubectl delete -f thingsboard.yml |
k8s/k8s-deploy-resources.sh
0 → 100755
1 | +#!/bin/bash | ||
2 | +# | ||
3 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +set -e | ||
19 | + | ||
20 | +kubectl apply -f tb-namespace.yml | ||
21 | +kubectl config set-context $(kubectl config current-context) --namespace=thingsboard | ||
22 | +kubectl apply -f tb-node-configmap.yml | ||
23 | +kubectl apply -f tb-mqtt-transport-configmap.yml | ||
24 | +kubectl apply -f tb-http-transport-configmap.yml | ||
25 | +kubectl apply -f tb-coap-transport-configmap.yml | ||
26 | +kubectl apply -f thingsboard.yml |
k8s/k8s-install-tb.sh
0 → 100755
1 | +#!/bin/bash | ||
2 | +# | ||
3 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +function installTb() { | ||
19 | + | ||
20 | + loadDemo=$1 | ||
21 | + | ||
22 | + kubectl apply -f tb-node-configmap.yml | ||
23 | + kubectl apply -f database-setup.yml && | ||
24 | + kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s && | ||
25 | + kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /install-finished;' | ||
26 | + | ||
27 | + kubectl delete pod tb-db-setup | ||
28 | + | ||
29 | +} | ||
30 | + | ||
31 | +function installPostgres() { | ||
32 | + | ||
33 | + kubectl apply -f postgres.yml | ||
34 | + kubectl apply -f tb-node-postgres-configmap.yml | ||
35 | + | ||
36 | + kubectl rollout status deployment/postgres | ||
37 | +} | ||
38 | + | ||
39 | +function installCassandra() { | ||
40 | + | ||
41 | + kubectl apply -f cassandra.yml | ||
42 | + kubectl apply -f tb-node-cassandra-configmap.yml | ||
43 | + | ||
44 | + kubectl rollout status statefulset/cassandra | ||
45 | + | ||
46 | + kubectl exec -it cassandra-0 -- bash -c "cqlsh -e \ | ||
47 | + \"CREATE KEYSPACE IF NOT EXISTS thingsboard \ | ||
48 | + WITH replication = { \ | ||
49 | + 'class' : 'SimpleStrategy', \ | ||
50 | + 'replication_factor' : 1 \ | ||
51 | + };\"" | ||
52 | +} | ||
53 | + | ||
54 | +while [[ $# -gt 0 ]] | ||
55 | +do | ||
56 | +key="$1" | ||
57 | + | ||
58 | +case $key in | ||
59 | + --loadDemo) | ||
60 | + LOAD_DEMO=true | ||
61 | + shift # past argument | ||
62 | + ;; | ||
63 | + *) | ||
64 | + # unknown option | ||
65 | + ;; | ||
66 | +esac | ||
67 | +shift # past argument or value | ||
68 | +done | ||
69 | + | ||
70 | +if [ "$LOAD_DEMO" == "true" ]; then | ||
71 | + loadDemo=true | ||
72 | +else | ||
73 | + loadDemo=false | ||
74 | +fi | ||
75 | + | ||
76 | +source .env | ||
77 | + | ||
78 | +kubectl apply -f tb-namespace.yml | ||
79 | +kubectl config set-context $(kubectl config current-context) --namespace=thingsboard | ||
80 | + | ||
81 | +case $DATABASE in | ||
82 | + postgres) | ||
83 | + installPostgres | ||
84 | + installTb ${loadDemo} | ||
85 | + ;; | ||
86 | + cassandra) | ||
87 | + installCassandra | ||
88 | + installTb ${loadDemo} | ||
89 | + ;; | ||
90 | + *) | ||
91 | + echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2 | ||
92 | + exit 1 | ||
93 | +esac |
k8s/k8s-upgrade-tb.sh
0 → 100755
1 | +#!/bin/bash | ||
2 | +# | ||
3 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
4 | +# | ||
5 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
6 | +# you may not use this file except in compliance with the License. | ||
7 | +# You may obtain a copy of the License at | ||
8 | +# | ||
9 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
10 | +# | ||
11 | +# Unless required by applicable law or agreed to in writing, software | ||
12 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
14 | +# See the License for the specific language governing permissions and | ||
15 | +# limitations under the License. | ||
16 | +# | ||
17 | + | ||
18 | +for i in "$@" | ||
19 | +do | ||
20 | +case $i in | ||
21 | + --fromVersion=*) | ||
22 | + FROM_VERSION="${i#*=}" | ||
23 | + shift | ||
24 | + ;; | ||
25 | + *) | ||
26 | + # unknown option | ||
27 | + ;; | ||
28 | +esac | ||
29 | +done | ||
30 | + | ||
31 | +if [[ -z "${FROM_VERSION// }" ]]; then | ||
32 | + echo "--fromVersion parameter is invalid or unspecified!" | ||
33 | + echo "Usage: k8s-upgrade-tb.sh --fromVersion={VERSION}" | ||
34 | + exit 1 | ||
35 | +else | ||
36 | + fromVersion="${FROM_VERSION// }" | ||
37 | +fi | ||
38 | + | ||
39 | +kubectl apply -f database-setup.yml && | ||
40 | +kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s && | ||
41 | +kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /install-finished;' | ||
42 | + | ||
43 | +kubectl delete pod tb-db-setup |
k8s/postgres.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: PersistentVolumeClaim | ||
19 | +metadata: | ||
20 | + name: postgres-pv-claim | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + app: postgres | ||
24 | +spec: | ||
25 | + accessModes: | ||
26 | + - ReadWriteOnce | ||
27 | + resources: | ||
28 | + requests: | ||
29 | + storage: 5Gi | ||
30 | +--- | ||
31 | +apiVersion: extensions/v1beta1 | ||
32 | +kind: Deployment | ||
33 | +metadata: | ||
34 | + name: postgres | ||
35 | + namespace: thingsboard | ||
36 | + labels: | ||
37 | + app: postgres | ||
38 | +spec: | ||
39 | + template: | ||
40 | + metadata: | ||
41 | + labels: | ||
42 | + app: postgres | ||
43 | + spec: | ||
44 | + volumes: | ||
45 | + - name: postgres-data | ||
46 | + persistentVolumeClaim: | ||
47 | + claimName: postgres-pv-claim | ||
48 | + containers: | ||
49 | + - name: postgres | ||
50 | + imagePullPolicy: Always | ||
51 | + image: postgres:9.6 | ||
52 | + ports: | ||
53 | + - containerPort: 5432 | ||
54 | + name: postgres | ||
55 | + env: | ||
56 | + - name: POSTGRES_DB | ||
57 | + value: "thingsboard" | ||
58 | + - name: PGDATA | ||
59 | + value: /var/lib/postgresql/data/pgdata | ||
60 | + volumeMounts: | ||
61 | + - mountPath: /var/lib/postgresql/data | ||
62 | + name: postgres-data | ||
63 | + livenessProbe: | ||
64 | + exec: | ||
65 | + command: | ||
66 | + - pg_isready | ||
67 | + - -h | ||
68 | + - localhost | ||
69 | + - -U | ||
70 | + - postgres | ||
71 | + initialDelaySeconds: 60 | ||
72 | + timeoutSeconds: 30 | ||
73 | + readinessProbe: | ||
74 | + exec: | ||
75 | + command: | ||
76 | + - pg_isready | ||
77 | + - -h | ||
78 | + - localhost | ||
79 | + - -U | ||
80 | + - postgres | ||
81 | + initialDelaySeconds: 5 | ||
82 | + timeoutSeconds: 1 | ||
83 | + restartPolicy: Always | ||
84 | +--- | ||
85 | +apiVersion: v1 | ||
86 | +kind: Service | ||
87 | +metadata: | ||
88 | + name: tb-database | ||
89 | + namespace: thingsboard | ||
90 | +spec: | ||
91 | + type: ClusterIP | ||
92 | + selector: | ||
93 | + app: postgres | ||
94 | + ports: | ||
95 | + - port: 5432 | ||
96 | + name: postgres | ||
97 | +--- |
k8s/tb-coap-transport-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-coap-transport-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-coap-transport-config | ||
24 | +data: | ||
25 | + conf: | | ||
26 | + export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | ||
27 | + export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | ||
28 | + export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | ||
29 | + export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | ||
30 | + export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | ||
31 | + export LOG_FILENAME=tb-coap-transport.out | ||
32 | + export LOADER_PATH=/usr/share/tb-coap-transport/conf | ||
33 | + logback: | | ||
34 | + <!DOCTYPE configuration> | ||
35 | + <configuration scan="true" scanPeriod="10 seconds"> | ||
36 | + | ||
37 | + <appender name="fileLogAppender" | ||
38 | + class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
39 | + <file>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.log</file> | ||
40 | + <rollingPolicy | ||
41 | + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | ||
42 | + <fileNamePattern>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||
43 | + <maxFileSize>100MB</maxFileSize> | ||
44 | + <maxHistory>30</maxHistory> | ||
45 | + <totalSizeCap>3GB</totalSizeCap> | ||
46 | + </rollingPolicy> | ||
47 | + <encoder> | ||
48 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
49 | + </encoder> | ||
50 | + </appender> | ||
51 | + | ||
52 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
53 | + <encoder> | ||
54 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
55 | + </encoder> | ||
56 | + </appender> | ||
57 | + | ||
58 | + <logger name="org.thingsboard.server" level="INFO" /> | ||
59 | + | ||
60 | + <root level="INFO"> | ||
61 | + <appender-ref ref="fileLogAppender"/> | ||
62 | + <appender-ref ref="STDOUT"/> | ||
63 | + </root> | ||
64 | + | ||
65 | + </configuration> |
k8s/tb-http-transport-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-http-transport-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-http-transport-config | ||
24 | +data: | ||
25 | + conf: | | ||
26 | + export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | ||
27 | + export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | ||
28 | + export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | ||
29 | + export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | ||
30 | + export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | ||
31 | + export LOG_FILENAME=tb-http-transport.out | ||
32 | + export LOADER_PATH=/usr/share/tb-http-transport/conf | ||
33 | + logback: | | ||
34 | + <!DOCTYPE configuration> | ||
35 | + <configuration scan="true" scanPeriod="10 seconds"> | ||
36 | + | ||
37 | + <appender name="fileLogAppender" | ||
38 | + class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
39 | + <file>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.log</file> | ||
40 | + <rollingPolicy | ||
41 | + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | ||
42 | + <fileNamePattern>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||
43 | + <maxFileSize>100MB</maxFileSize> | ||
44 | + <maxHistory>30</maxHistory> | ||
45 | + <totalSizeCap>3GB</totalSizeCap> | ||
46 | + </rollingPolicy> | ||
47 | + <encoder> | ||
48 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
49 | + </encoder> | ||
50 | + </appender> | ||
51 | + | ||
52 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
53 | + <encoder> | ||
54 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
55 | + </encoder> | ||
56 | + </appender> | ||
57 | + | ||
58 | + <logger name="org.thingsboard.server" level="INFO" /> | ||
59 | + | ||
60 | + <root level="INFO"> | ||
61 | + <appender-ref ref="fileLogAppender"/> | ||
62 | + <appender-ref ref="STDOUT"/> | ||
63 | + </root> | ||
64 | + | ||
65 | + </configuration> |
k8s/tb-mqtt-transport-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-mqtt-transport-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-mqtt-transport-config | ||
24 | +data: | ||
25 | + conf: | | ||
26 | + export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | ||
27 | + export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | ||
28 | + export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | ||
29 | + export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | ||
30 | + export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | ||
31 | + export LOG_FILENAME=tb-mqtt-transport.out | ||
32 | + export LOADER_PATH=/usr/share/tb-mqtt-transport/conf | ||
33 | + logback: | | ||
34 | + <!DOCTYPE configuration> | ||
35 | + <configuration scan="true" scanPeriod="10 seconds"> | ||
36 | + | ||
37 | + <appender name="fileLogAppender" | ||
38 | + class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
39 | + <file>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.log</file> | ||
40 | + <rollingPolicy | ||
41 | + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | ||
42 | + <fileNamePattern>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||
43 | + <maxFileSize>100MB</maxFileSize> | ||
44 | + <maxHistory>30</maxHistory> | ||
45 | + <totalSizeCap>3GB</totalSizeCap> | ||
46 | + </rollingPolicy> | ||
47 | + <encoder> | ||
48 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
49 | + </encoder> | ||
50 | + </appender> | ||
51 | + | ||
52 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
53 | + <encoder> | ||
54 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
55 | + </encoder> | ||
56 | + </appender> | ||
57 | + | ||
58 | + <logger name="org.thingsboard.server" level="INFO" /> | ||
59 | + | ||
60 | + <root level="INFO"> | ||
61 | + <appender-ref ref="fileLogAppender"/> | ||
62 | + <appender-ref ref="STDOUT"/> | ||
63 | + </root> | ||
64 | + | ||
65 | + </configuration> |
k8s/tb-namespace.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: Namespace | ||
19 | +metadata: | ||
20 | + name: thingsboard | ||
21 | + labels: | ||
22 | + name: thingsboard |
k8s/tb-node-cassandra-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-node-db-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-node-db-config | ||
24 | +data: | ||
25 | + DATABASE_TS_TYPE: cassandra | ||
26 | + DATABASE_ENTITIES_TYPE: cassandra | ||
27 | + CASSANDRA_URL: cassandra:9042 | ||
28 | + CASSANDRA_SOCKET_READ_TIMEOUT: "60000" |
k8s/tb-node-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-node-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-node-config | ||
24 | +data: | ||
25 | + conf: | | ||
26 | + export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data" | ||
27 | + export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps" | ||
28 | + export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10" | ||
29 | + export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" | ||
30 | + export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled" | ||
31 | + export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError" | ||
32 | + export LOG_FILENAME=thingsboard.out | ||
33 | + export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions | ||
34 | + logback: | | ||
35 | + <!DOCTYPE configuration> | ||
36 | + <configuration scan="true" scanPeriod="10 seconds"> | ||
37 | + | ||
38 | + <appender name="fileLogAppender" | ||
39 | + class="ch.qos.logback.core.rolling.RollingFileAppender"> | ||
40 | + <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file> | ||
41 | + <rollingPolicy | ||
42 | + class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | ||
43 | + <fileNamePattern>/var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern> | ||
44 | + <maxFileSize>100MB</maxFileSize> | ||
45 | + <maxHistory>30</maxHistory> | ||
46 | + <totalSizeCap>3GB</totalSizeCap> | ||
47 | + </rollingPolicy> | ||
48 | + <encoder> | ||
49 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
50 | + </encoder> | ||
51 | + </appender> | ||
52 | + | ||
53 | + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | ||
54 | + <encoder> | ||
55 | + <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> | ||
56 | + </encoder> | ||
57 | + </appender> | ||
58 | + | ||
59 | + <logger name="org.thingsboard.server" level="INFO" /> | ||
60 | + <logger name="com.google.common.util.concurrent.AggregateFuture" level="OFF" /> | ||
61 | + | ||
62 | + <root level="INFO"> | ||
63 | + <appender-ref ref="fileLogAppender"/> | ||
64 | + <appender-ref ref="STDOUT"/> | ||
65 | + </root> | ||
66 | + | ||
67 | + </configuration> |
k8s/tb-node-postgres-configmap.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: v1 | ||
18 | +kind: ConfigMap | ||
19 | +metadata: | ||
20 | + name: tb-node-db-config | ||
21 | + namespace: thingsboard | ||
22 | + labels: | ||
23 | + name: tb-node-db-config | ||
24 | +data: | ||
25 | + DATABASE_TS_TYPE: sql | ||
26 | + DATABASE_ENTITIES_TYPE: sql | ||
27 | + SPRING_JPA_DATABASE_PLATFORM: org.hibernate.dialect.PostgreSQLDialect | ||
28 | + SPRING_DRIVER_CLASS_NAME: org.postgresql.Driver | ||
29 | + SPRING_DATASOURCE_URL: jdbc:postgresql://tb-database:5432/thingsboard | ||
30 | + SPRING_DATASOURCE_USERNAME: postgres | ||
31 | + SPRING_DATASOURCE_PASSWORD: postgres |
k8s/thingsboard.yml
0 → 100644
1 | +# | ||
2 | +# Copyright © 2016-2019 The Thingsboard Authors | ||
3 | +# | ||
4 | +# Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | +# you may not use this file except in compliance with the License. | ||
6 | +# You may obtain a copy of the License at | ||
7 | +# | ||
8 | +# http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | +# | ||
10 | +# Unless required by applicable law or agreed to in writing, software | ||
11 | +# distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | +# See the License for the specific language governing permissions and | ||
14 | +# limitations under the License. | ||
15 | +# | ||
16 | + | ||
17 | +apiVersion: extensions/v1beta1 | ||
18 | +kind: Deployment | ||
19 | +metadata: | ||
20 | + name: zookeeper | ||
21 | + namespace: thingsboard | ||
22 | +spec: | ||
23 | + template: | ||
24 | + metadata: | ||
25 | + labels: | ||
26 | + app: zookeeper | ||
27 | + spec: | ||
28 | + containers: | ||
29 | + - name: server | ||
30 | + imagePullPolicy: Always | ||
31 | + image: zookeeper:3.5 | ||
32 | + ports: | ||
33 | + - containerPort: 2181 | ||
34 | + readinessProbe: | ||
35 | + periodSeconds: 5 | ||
36 | + tcpSocket: | ||
37 | + port: 2181 | ||
38 | + livenessProbe: | ||
39 | + periodSeconds: 5 | ||
40 | + tcpSocket: | ||
41 | + port: 2181 | ||
42 | + env: | ||
43 | + - name: ZOO_MY_ID | ||
44 | + value: "1" | ||
45 | + - name: ZOO_SERVERS | ||
46 | + value: "server.1=0.0.0.0:2888:3888;0.0.0.0:2181" | ||
47 | + restartPolicy: Always | ||
48 | +--- | ||
49 | +apiVersion: v1 | ||
50 | +kind: Service | ||
51 | +metadata: | ||
52 | + name: zookeeper | ||
53 | + namespace: thingsboard | ||
54 | +spec: | ||
55 | + type: ClusterIP | ||
56 | + selector: | ||
57 | + app: zookeeper | ||
58 | + ports: | ||
59 | + - name: zk-port | ||
60 | + port: 2181 | ||
61 | +--- | ||
62 | +apiVersion: extensions/v1beta1 | ||
63 | +kind: Deployment | ||
64 | +metadata: | ||
65 | + name: tb-kafka | ||
66 | + namespace: thingsboard | ||
67 | +spec: | ||
68 | + template: | ||
69 | + metadata: | ||
70 | + labels: | ||
71 | + app: tb-kafka | ||
72 | + spec: | ||
73 | + containers: | ||
74 | + - name: server | ||
75 | + imagePullPolicy: Always | ||
76 | + image: wurstmeister/kafka | ||
77 | + ports: | ||
78 | + - containerPort: 9092 | ||
79 | + readinessProbe: | ||
80 | + periodSeconds: 20 | ||
81 | + tcpSocket: | ||
82 | + port: 9092 | ||
83 | + livenessProbe: | ||
84 | + periodSeconds: 5 | ||
85 | + tcpSocket: | ||
86 | + port: 9092 | ||
87 | + env: | ||
88 | + - name: KAFKA_ZOOKEEPER_CONNECT | ||
89 | + value: "zookeeper:2181" | ||
90 | + - name: KAFKA_LISTENERS | ||
91 | + value: "INSIDE://:9093,OUTSIDE://:9092" | ||
92 | + - name: KAFKA_ADVERTISED_LISTENERS | ||
93 | + value: "INSIDE://:9093,OUTSIDE://tb-kafka:9092" | ||
94 | + - name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP | ||
95 | + value: "INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT" | ||
96 | + - name: KAFKA_INTER_BROKER_LISTENER_NAME | ||
97 | + value: "INSIDE" | ||
98 | + - name: KAFKA_CREATE_TOPICS | ||
99 | + value: "js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600" | ||
100 | + - name: KAFKA_AUTO_CREATE_TOPICS_ENABLE | ||
101 | + value: "false" | ||
102 | + - name: KAFKA_LOG_RETENTION_BYTES | ||
103 | + value: "1073741824" | ||
104 | + - name: KAFKA_LOG_SEGMENT_BYTES | ||
105 | + value: "268435456" | ||
106 | + - name: KAFKA_LOG_RETENTION_MS | ||
107 | + value: "300000" | ||
108 | + - name: KAFKA_LOG_CLEANUP_POLICY | ||
109 | + value: "delete" | ||
110 | + restartPolicy: Always | ||
111 | +--- | ||
112 | +apiVersion: v1 | ||
113 | +kind: Service | ||
114 | +metadata: | ||
115 | + name: tb-kafka | ||
116 | + namespace: thingsboard | ||
117 | +spec: | ||
118 | + type: ClusterIP | ||
119 | + selector: | ||
120 | + app: tb-kafka | ||
121 | + ports: | ||
122 | + - name: tb-kafka-port | ||
123 | + port: 9092 | ||
124 | +--- | ||
125 | +apiVersion: extensions/v1beta1 | ||
126 | +kind: Deployment | ||
127 | +metadata: | ||
128 | + name: tb-redis | ||
129 | + namespace: thingsboard | ||
130 | +spec: | ||
131 | + template: | ||
132 | + metadata: | ||
133 | + labels: | ||
134 | + app: tb-redis | ||
135 | + spec: | ||
136 | + containers: | ||
137 | + - name: server | ||
138 | + imagePullPolicy: Always | ||
139 | + image: redis:4.0 | ||
140 | + ports: | ||
141 | + - containerPort: 6379 | ||
142 | + readinessProbe: | ||
143 | + periodSeconds: 5 | ||
144 | + tcpSocket: | ||
145 | + port: 6379 | ||
146 | + livenessProbe: | ||
147 | + periodSeconds: 5 | ||
148 | + tcpSocket: | ||
149 | + port: 6379 | ||
150 | + volumeMounts: | ||
151 | + - mountPath: /data | ||
152 | + name: redis-data | ||
153 | + volumes: | ||
154 | + - name: redis-data | ||
155 | + emptyDir: {} | ||
156 | + restartPolicy: Always | ||
157 | +--- | ||
158 | +apiVersion: v1 | ||
159 | +kind: Service | ||
160 | +metadata: | ||
161 | + name: tb-redis | ||
162 | + namespace: thingsboard | ||
163 | +spec: | ||
164 | + type: ClusterIP | ||
165 | + selector: | ||
166 | + app: tb-redis | ||
167 | + ports: | ||
168 | + - name: tb-redis-port | ||
169 | + port: 6379 | ||
170 | +--- | ||
171 | +apiVersion: extensions/v1beta1 | ||
172 | +kind: Deployment | ||
173 | +metadata: | ||
174 | + name: tb-js-executor | ||
175 | + namespace: thingsboard | ||
176 | +spec: | ||
177 | + replicas: 20 | ||
178 | + selector: | ||
179 | + matchLabels: | ||
180 | + app: tb-js-executor | ||
181 | + template: | ||
182 | + metadata: | ||
183 | + labels: | ||
184 | + app: tb-js-executor | ||
185 | + spec: | ||
186 | + containers: | ||
187 | + - name: server | ||
188 | + imagePullPolicy: Always | ||
189 | + image: thingsboard/tb-js-executor:latest | ||
190 | + env: | ||
191 | + - name: REMOTE_JS_EVAL_REQUEST_TOPIC | ||
192 | + value: "js.eval.requests" | ||
193 | + - name: TB_KAFKA_SERVERS | ||
194 | + value: "tb-kafka:9092" | ||
195 | + - name: LOGGER_LEVEL | ||
196 | + value: "info" | ||
197 | + - name: LOG_FOLDER | ||
198 | + value: "logs" | ||
199 | + - name: LOGGER_FILENAME | ||
200 | + value: "tb-js-executor-%DATE%.log" | ||
201 | + - name: DOCKER_MODE | ||
202 | + value: "true" | ||
203 | + - name: SCRIPT_BODY_TRACE_FREQUENCY | ||
204 | + value: "1000" | ||
205 | + restartPolicy: Always | ||
206 | +--- | ||
207 | +apiVersion: extensions/v1beta1 | ||
208 | +kind: Deployment | ||
209 | +metadata: | ||
210 | + name: tb-node | ||
211 | + namespace: thingsboard | ||
212 | +spec: | ||
213 | + replicas: 2 | ||
214 | + selector: | ||
215 | + matchLabels: | ||
216 | + app: tb-node | ||
217 | + template: | ||
218 | + metadata: | ||
219 | + labels: | ||
220 | + app: tb-node | ||
221 | + spec: | ||
222 | + volumes: | ||
223 | + - name: tb-node-config | ||
224 | + configMap: | ||
225 | + name: tb-node-config | ||
226 | + items: | ||
227 | + - key: conf | ||
228 | + path: thingsboard.conf | ||
229 | + - key: logback | ||
230 | + path: logback.xml | ||
231 | + containers: | ||
232 | + - name: server | ||
233 | + imagePullPolicy: Always | ||
234 | + image: thingsboard/tb-node:latest | ||
235 | + ports: | ||
236 | + - containerPort: 8080 | ||
237 | + name: http | ||
238 | + - containerPort: 9001 | ||
239 | + name: rpc | ||
240 | + env: | ||
241 | + - name: RPC_HOST | ||
242 | + valueFrom: | ||
243 | + fieldRef: | ||
244 | + fieldPath: status.podIP | ||
245 | + - name: CLUSTER_NODE_ID | ||
246 | + valueFrom: | ||
247 | + fieldRef: | ||
248 | + fieldPath: metadata.name | ||
249 | + - name: TB_HOST | ||
250 | + valueFrom: | ||
251 | + fieldRef: | ||
252 | + fieldPath: metadata.name | ||
253 | + - name: ZOOKEEPER_ENABLED | ||
254 | + value: "true" | ||
255 | + - name: ZOOKEEPER_URL | ||
256 | + value: "zookeeper:2181" | ||
257 | + - name: TB_KAFKA_SERVERS | ||
258 | + value: "tb-kafka:9092" | ||
259 | + - name: JS_EVALUATOR | ||
260 | + value: "remote" | ||
261 | + - name: TRANSPORT_TYPE | ||
262 | + value: "remote" | ||
263 | + - name: CACHE_TYPE | ||
264 | + value: "redis" | ||
265 | + - name: REDIS_HOST | ||
266 | + value: "tb-redis" | ||
267 | + - name: HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE | ||
268 | + value: "false" | ||
269 | + envFrom: | ||
270 | + - configMapRef: | ||
271 | + name: tb-node-db-config | ||
272 | + volumeMounts: | ||
273 | + - mountPath: /config | ||
274 | + name: tb-node-config | ||
275 | + livenessProbe: | ||
276 | + httpGet: | ||
277 | + path: /login | ||
278 | + port: http | ||
279 | + initialDelaySeconds: 120 | ||
280 | + timeoutSeconds: 10 | ||
281 | + restartPolicy: Always | ||
282 | +--- | ||
283 | +apiVersion: v1 | ||
284 | +kind: Service | ||
285 | +metadata: | ||
286 | + name: tb-node | ||
287 | + namespace: thingsboard | ||
288 | +spec: | ||
289 | + type: ClusterIP | ||
290 | + selector: | ||
291 | + app: tb-node | ||
292 | + ports: | ||
293 | + - port: 8080 | ||
294 | + name: http | ||
295 | +--- | ||
296 | +apiVersion: extensions/v1beta1 | ||
297 | +kind: Deployment | ||
298 | +metadata: | ||
299 | + name: tb-mqtt-transport | ||
300 | + namespace: thingsboard | ||
301 | +spec: | ||
302 | + replicas: 2 | ||
303 | + selector: | ||
304 | + matchLabels: | ||
305 | + app: tb-mqtt-transport | ||
306 | + template: | ||
307 | + metadata: | ||
308 | + labels: | ||
309 | + app: tb-mqtt-transport | ||
310 | + spec: | ||
311 | + volumes: | ||
312 | + - name: tb-mqtt-transport-config | ||
313 | + configMap: | ||
314 | + name: tb-mqtt-transport-config | ||
315 | + items: | ||
316 | + - key: conf | ||
317 | + path: tb-mqtt-transport.conf | ||
318 | + - key: logback | ||
319 | + path: logback.xml | ||
320 | + containers: | ||
321 | + - name: server | ||
322 | + imagePullPolicy: Always | ||
323 | + image: thingsboard/tb-mqtt-transport:latest | ||
324 | + ports: | ||
325 | + - containerPort: 1883 | ||
326 | + name: mqtt | ||
327 | + env: | ||
328 | + - name: CLUSTER_NODE_ID | ||
329 | + valueFrom: | ||
330 | + fieldRef: | ||
331 | + fieldPath: metadata.name | ||
332 | + - name: TB_HOST | ||
333 | + valueFrom: | ||
334 | + fieldRef: | ||
335 | + fieldPath: metadata.name | ||
336 | + - name: MQTT_BIND_ADDRESS | ||
337 | + value: "0.0.0.0" | ||
338 | + - name: MQTT_BIND_PORT | ||
339 | + value: "1883" | ||
340 | + - name: MQTT_TIMEOUT | ||
341 | + value: "10000" | ||
342 | + - name: TB_KAFKA_SERVERS | ||
343 | + value: "tb-kafka:9092" | ||
344 | + volumeMounts: | ||
345 | + - mountPath: /config | ||
346 | + name: tb-mqtt-transport-config | ||
347 | + readinessProbe: | ||
348 | + periodSeconds: 20 | ||
349 | + tcpSocket: | ||
350 | + port: 1883 | ||
351 | + livenessProbe: | ||
352 | + periodSeconds: 20 | ||
353 | + tcpSocket: | ||
354 | + port: 1883 | ||
355 | + restartPolicy: Always | ||
356 | +--- | ||
357 | +apiVersion: v1 | ||
358 | +kind: Service | ||
359 | +metadata: | ||
360 | + name: tb-mqtt-transport | ||
361 | + namespace: thingsboard | ||
362 | +spec: | ||
363 | + type: LoadBalancer | ||
364 | + selector: | ||
365 | + app: tb-mqtt-transport | ||
366 | + ports: | ||
367 | + - port: 1883 | ||
368 | + targetPort: 1883 | ||
369 | + name: mqtt | ||
370 | +--- | ||
371 | +apiVersion: extensions/v1beta1 | ||
372 | +kind: Deployment | ||
373 | +metadata: | ||
374 | + name: tb-http-transport | ||
375 | + namespace: thingsboard | ||
376 | +spec: | ||
377 | + replicas: 2 | ||
378 | + selector: | ||
379 | + matchLabels: | ||
380 | + app: tb-http-transport | ||
381 | + template: | ||
382 | + metadata: | ||
383 | + labels: | ||
384 | + app: tb-http-transport | ||
385 | + spec: | ||
386 | + volumes: | ||
387 | + - name: tb-http-transport-config | ||
388 | + configMap: | ||
389 | + name: tb-http-transport-config | ||
390 | + items: | ||
391 | + - key: conf | ||
392 | + path: tb-http-transport.conf | ||
393 | + - key: logback | ||
394 | + path: logback.xml | ||
395 | + containers: | ||
396 | + - name: server | ||
397 | + imagePullPolicy: Always | ||
398 | + image: thingsboard/tb-http-transport:latest | ||
399 | + ports: | ||
400 | + - containerPort: 8080 | ||
401 | + name: http | ||
402 | + env: | ||
403 | + - name: CLUSTER_NODE_ID | ||
404 | + valueFrom: | ||
405 | + fieldRef: | ||
406 | + fieldPath: metadata.name | ||
407 | + - name: TB_HOST | ||
408 | + valueFrom: | ||
409 | + fieldRef: | ||
410 | + fieldPath: metadata.name | ||
411 | + - name: HTTP_BIND_ADDRESS | ||
412 | + value: "0.0.0.0" | ||
413 | + - name: HTTP_BIND_PORT | ||
414 | + value: "8080" | ||
415 | + - name: HTTP_REQUEST_TIMEOUT | ||
416 | + value: "60000" | ||
417 | + - name: TB_KAFKA_SERVERS | ||
418 | + value: "tb-kafka:9092" | ||
419 | + volumeMounts: | ||
420 | + - mountPath: /config | ||
421 | + name: tb-http-transport-config | ||
422 | + readinessProbe: | ||
423 | + periodSeconds: 20 | ||
424 | + tcpSocket: | ||
425 | + port: 8080 | ||
426 | + livenessProbe: | ||
427 | + periodSeconds: 20 | ||
428 | + tcpSocket: | ||
429 | + port: 8080 | ||
430 | + restartPolicy: Always | ||
431 | +--- | ||
432 | +apiVersion: v1 | ||
433 | +kind: Service | ||
434 | +metadata: | ||
435 | + name: tb-http-transport | ||
436 | + namespace: thingsboard | ||
437 | +spec: | ||
438 | + type: ClusterIP | ||
439 | + selector: | ||
440 | + app: tb-http-transport | ||
441 | + ports: | ||
442 | + - port: 8080 | ||
443 | + name: http | ||
444 | +--- | ||
445 | +apiVersion: extensions/v1beta1 | ||
446 | +kind: Deployment | ||
447 | +metadata: | ||
448 | + name: tb-coap-transport | ||
449 | + namespace: thingsboard | ||
450 | +spec: | ||
451 | + replicas: 2 | ||
452 | + selector: | ||
453 | + matchLabels: | ||
454 | + app: tb-coap-transport | ||
455 | + template: | ||
456 | + metadata: | ||
457 | + labels: | ||
458 | + app: tb-coap-transport | ||
459 | + spec: | ||
460 | + volumes: | ||
461 | + - name: tb-coap-transport-config | ||
462 | + configMap: | ||
463 | + name: tb-coap-transport-config | ||
464 | + items: | ||
465 | + - key: conf | ||
466 | + path: tb-coap-transport.conf | ||
467 | + - key: logback | ||
468 | + path: logback.xml | ||
469 | + containers: | ||
470 | + - name: server | ||
471 | + imagePullPolicy: Always | ||
472 | + image: thingsboard/tb-coap-transport:latest | ||
473 | + ports: | ||
474 | + - containerPort: 5683 | ||
475 | + name: coap | ||
476 | + protocol: UDP | ||
477 | + env: | ||
478 | + - name: CLUSTER_NODE_ID | ||
479 | + valueFrom: | ||
480 | + fieldRef: | ||
481 | + fieldPath: metadata.name | ||
482 | + - name: TB_HOST | ||
483 | + valueFrom: | ||
484 | + fieldRef: | ||
485 | + fieldPath: metadata.name | ||
486 | + - name: COAP_BIND_ADDRESS | ||
487 | + value: "0.0.0.0" | ||
488 | + - name: COAP_BIND_PORT | ||
489 | + value: "5683" | ||
490 | + - name: COAP_TIMEOUT | ||
491 | + value: "10000" | ||
492 | + - name: TB_KAFKA_SERVERS | ||
493 | + value: "tb-kafka:9092" | ||
494 | + volumeMounts: | ||
495 | + - mountPath: /config | ||
496 | + name: tb-coap-transport-config | ||
497 | + restartPolicy: Always | ||
498 | +--- | ||
499 | +apiVersion: v1 | ||
500 | +kind: Service | ||
501 | +metadata: | ||
502 | + name: tb-coap-transport | ||
503 | + namespace: thingsboard | ||
504 | +spec: | ||
505 | + type: LoadBalancer | ||
506 | + selector: | ||
507 | + app: tb-coap-transport | ||
508 | + ports: | ||
509 | + - port: 5683 | ||
510 | + name: coap | ||
511 | + protocol: UDP | ||
512 | +--- | ||
513 | +apiVersion: extensions/v1beta1 | ||
514 | +kind: Deployment | ||
515 | +metadata: | ||
516 | + name: tb-web-ui | ||
517 | + namespace: thingsboard | ||
518 | +spec: | ||
519 | + replicas: 2 | ||
520 | + selector: | ||
521 | + matchLabels: | ||
522 | + app: tb-web-ui | ||
523 | + template: | ||
524 | + metadata: | ||
525 | + labels: | ||
526 | + app: tb-web-ui | ||
527 | + spec: | ||
528 | + containers: | ||
529 | + - name: server | ||
530 | + imagePullPolicy: Always | ||
531 | + image: thingsboard/tb-web-ui:latest | ||
532 | + ports: | ||
533 | + - containerPort: 8080 | ||
534 | + name: http | ||
535 | + env: | ||
536 | + - name: HTTP_BIND_ADDRESS | ||
537 | + value: "0.0.0.0" | ||
538 | + - name: HTTP_BIND_PORT | ||
539 | + value: "8080" | ||
540 | + - name: TB_ENABLE_PROXY | ||
541 | + value: "false" | ||
542 | + - name: LOGGER_LEVEL | ||
543 | + value: "info" | ||
544 | + - name: LOG_FOLDER | ||
545 | + value: "logs" | ||
546 | + - name: LOGGER_FILENAME | ||
547 | + value: "tb-web-ui-%DATE%.log" | ||
548 | + - name: DOCKER_MODE | ||
549 | + value: "true" | ||
550 | + livenessProbe: | ||
551 | + httpGet: | ||
552 | + path: /index.html | ||
553 | + port: http | ||
554 | + initialDelaySeconds: 120 | ||
555 | + timeoutSeconds: 10 | ||
556 | + restartPolicy: Always | ||
557 | +--- | ||
558 | +apiVersion: v1 | ||
559 | +kind: Service | ||
560 | +metadata: | ||
561 | + name: tb-web-ui | ||
562 | + namespace: thingsboard | ||
563 | +spec: | ||
564 | + type: ClusterIP | ||
565 | + selector: | ||
566 | + app: tb-web-ui | ||
567 | + ports: | ||
568 | + - port: 8080 | ||
569 | + name: http | ||
570 | +--- | ||
571 | +apiVersion: extensions/v1beta1 | ||
572 | +kind: Ingress | ||
573 | +metadata: | ||
574 | + name: tb-ingress | ||
575 | + namespace: thingsboard | ||
576 | + annotations: | ||
577 | + nginx.ingress.kubernetes.io/use-regex: "true" | ||
578 | + nginx.ingress.kubernetes.io/ssl-redirect: "false" | ||
579 | + nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" | ||
580 | +spec: | ||
581 | + rules: | ||
582 | + - http: | ||
583 | + paths: | ||
584 | + - path: /api/v1/.* | ||
585 | + backend: | ||
586 | + serviceName: tb-http-transport | ||
587 | + servicePort: 8080 | ||
588 | + - path: /static/rulenode/.* | ||
589 | + backend: | ||
590 | + serviceName: tb-node | ||
591 | + servicePort: 8080 | ||
592 | + - path: /static/.* | ||
593 | + backend: | ||
594 | + serviceName: tb-web-ui | ||
595 | + servicePort: 8080 | ||
596 | + - path: /index.html.* | ||
597 | + backend: | ||
598 | + serviceName: tb-web-ui | ||
599 | + servicePort: 8080 | ||
600 | + - path: / | ||
601 | + backend: | ||
602 | + serviceName: tb-web-ui | ||
603 | + servicePort: 8080 | ||
604 | + - path: /.* | ||
605 | + backend: | ||
606 | + serviceName: tb-node | ||
607 | + servicePort: 8080 | ||
608 | +--- |
@@ -185,21 +185,21 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> | @@ -185,21 +185,21 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> | ||
185 | 185 | ||
186 | case AT_LEAST_ONCE: | 186 | case AT_LEAST_ONCE: |
187 | invokeHandlersForIncomingPublish(message); | 187 | invokeHandlersForIncomingPublish(message); |
188 | - if (message.variableHeader().messageId() != -1) { | 188 | + if (message.variableHeader().packetId() != -1) { |
189 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); | 189 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0); |
190 | - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); | 190 | + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().packetId()); |
191 | channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); | 191 | channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader)); |
192 | } | 192 | } |
193 | break; | 193 | break; |
194 | 194 | ||
195 | case EXACTLY_ONCE: | 195 | case EXACTLY_ONCE: |
196 | - if (message.variableHeader().messageId() != -1) { | 196 | + if (message.variableHeader().packetId() != -1) { |
197 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0); | 197 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0); |
198 | - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().messageId()); | 198 | + MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(message.variableHeader().packetId()); |
199 | MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader); | 199 | MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader); |
200 | 200 | ||
201 | MqttIncomingQos2Publish incomingQos2Publish = new MqttIncomingQos2Publish(message, pubrecMessage); | 201 | MqttIncomingQos2Publish incomingQos2Publish = new MqttIncomingQos2Publish(message, pubrecMessage); |
202 | - this.client.getQos2PendingIncomingPublishes().put(message.variableHeader().messageId(), incomingQos2Publish); | 202 | + this.client.getQos2PendingIncomingPublishes().put(message.variableHeader().packetId(), incomingQos2Publish); |
203 | message.payload().retain(); | 203 | message.payload().retain(); |
204 | incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); | 204 | incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket); |
205 | 205 | ||
@@ -249,7 +249,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> | @@ -249,7 +249,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage> | ||
249 | MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); | 249 | MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); |
250 | this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); | 250 | this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish()); |
251 | incomingQos2Publish.onPubrelReceived(); | 251 | incomingQos2Publish.onPubrelReceived(); |
252 | - this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().messageId()); | 252 | + this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId()); |
253 | } | 253 | } |
254 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); | 254 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0); |
255 | MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); | 255 | MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId()); |
@@ -339,7 +339,7 @@ final class MqttClientImpl implements MqttClient { | @@ -339,7 +339,7 @@ final class MqttClientImpl implements MqttClient { | ||
339 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0); | 339 | MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0); |
340 | MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); | 340 | MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); |
341 | MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); | 341 | MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); |
342 | - MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.messageId(), future, payload.retain(), message, qos); | 342 | + MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); |
343 | ChannelFuture channelFuture = this.sendAndFlushPacket(message); | 343 | ChannelFuture channelFuture = this.sendAndFlushPacket(message); |
344 | 344 | ||
345 | if (channelFuture != null) { | 345 | if (channelFuture != null) { |
@@ -29,10 +29,10 @@ | @@ -29,10 +29,10 @@ | ||
29 | 29 | ||
30 | <properties> | 30 | <properties> |
31 | <main.dir>${basedir}</main.dir> | 31 | <main.dir>${basedir}</main.dir> |
32 | - <spring-boot.version>1.4.3.RELEASE</spring-boot.version> | ||
33 | - <spring.version>4.3.4.RELEASE</spring.version> | ||
34 | - <spring-security.version>4.2.0.RELEASE</spring-security.version> | ||
35 | - <spring-data-redis.version>1.8.10.RELEASE</spring-data-redis.version> | 32 | + <spring-boot.version>2.1.3.RELEASE</spring-boot.version> |
33 | + <spring.version>5.1.5.RELEASE</spring.version> | ||
34 | + <spring-security.version>5.1.4.RELEASE</spring-security.version> | ||
35 | + <spring-data-redis.version>2.1.5.RELEASE</spring-data-redis.version> | ||
36 | <jedis.version>2.9.0</jedis.version> | 36 | <jedis.version>2.9.0</jedis.version> |
37 | <jjwt.version>0.7.0</jjwt.version> | 37 | <jjwt.version>0.7.0</jjwt.version> |
38 | <json-path.version>2.2.0</json-path.version> | 38 | <json-path.version>2.2.0</json-path.version> |
@@ -41,8 +41,8 @@ | @@ -41,8 +41,8 @@ | ||
41 | <logback.version>1.2.3</logback.version> | 41 | <logback.version>1.2.3</logback.version> |
42 | <mockito.version>1.9.5</mockito.version> | 42 | <mockito.version>1.9.5</mockito.version> |
43 | <rat.version>0.10</rat.version> | 43 | <rat.version>0.10</rat.version> |
44 | - <cassandra.version>3.5.0</cassandra.version> | ||
45 | - <cassandra-unit.version>3.3.0.2</cassandra-unit.version> | 44 | + <cassandra.version>3.6.0</cassandra.version> |
45 | + <cassandra-unit.version>3.5.0.1</cassandra-unit.version> | ||
46 | <takari-cpsuite.version>1.2.7</takari-cpsuite.version> | 46 | <takari-cpsuite.version>1.2.7</takari-cpsuite.version> |
47 | <guava.version>21.0</guava.version> | 47 | <guava.version>21.0</guava.version> |
48 | <caffeine.version>2.6.1</caffeine.version> | 48 | <caffeine.version>2.6.1</caffeine.version> |
@@ -50,7 +50,7 @@ | @@ -50,7 +50,7 @@ | ||
50 | <commons-validator.version>1.5.0</commons-validator.version> | 50 | <commons-validator.version>1.5.0</commons-validator.version> |
51 | <commons-io.version>2.5</commons-io.version> | 51 | <commons-io.version>2.5</commons-io.version> |
52 | <commons-csv.version>1.4</commons-csv.version> | 52 | <commons-csv.version>1.4</commons-csv.version> |
53 | - <jackson.version>2.8.11.1</jackson.version> | 53 | + <jackson.version>2.9.8</jackson.version> |
54 | <json-schema-validator.version>2.2.6</json-schema-validator.version> | 54 | <json-schema-validator.version>2.2.6</json-schema-validator.version> |
55 | <scala.version>2.11</scala.version> | 55 | <scala.version>2.11</scala.version> |
56 | <akka.version>2.4.2</akka.version> | 56 | <akka.version>2.4.2</akka.version> |
@@ -59,18 +59,20 @@ | @@ -59,18 +59,20 @@ | ||
59 | <velocity.version>1.7</velocity.version> | 59 | <velocity.version>1.7</velocity.version> |
60 | <velocity-tools.version>2.0</velocity-tools.version> | 60 | <velocity-tools.version>2.0</velocity-tools.version> |
61 | <mail.version>1.4.3</mail.version> | 61 | <mail.version>1.4.3</mail.version> |
62 | - <curator.version>4.0.1</curator.version> | ||
63 | - <protobuf.version>3.0.2</protobuf.version> | ||
64 | - <grpc.version>1.12.0</grpc.version> | 62 | + <curator.version>4.2.0</curator.version> |
63 | + <protobuf.version>3.6.1</protobuf.version> | ||
64 | + <grpc.version>1.19.0</grpc.version> | ||
65 | <lombok.version>1.16.18</lombok.version> | 65 | <lombok.version>1.16.18</lombok.version> |
66 | <paho.client.version>1.1.0</paho.client.version> | 66 | <paho.client.version>1.1.0</paho.client.version> |
67 | - <netty.version>4.1.22.Final</netty.version> | 67 | + <netty.version>4.1.34.Final</netty.version> |
68 | <os-maven-plugin.version>1.5.0</os-maven-plugin.version> | 68 | <os-maven-plugin.version>1.5.0</os-maven-plugin.version> |
69 | <rabbitmq.version>4.8.0</rabbitmq.version> | 69 | <rabbitmq.version>4.8.0</rabbitmq.version> |
70 | <surfire.version>2.19.1</surfire.version> | 70 | <surfire.version>2.19.1</surfire.version> |
71 | <jar-plugin.version>3.0.2</jar-plugin.version> | 71 | <jar-plugin.version>3.0.2</jar-plugin.version> |
72 | <springfox-swagger.version>2.6.1</springfox-swagger.version> | 72 | <springfox-swagger.version>2.6.1</springfox-swagger.version> |
73 | <springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version> | 73 | <springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version> |
74 | + <spatial4j.version>0.7</spatial4j.version> | ||
75 | + <jts.version>1.15.0</jts.version> | ||
74 | <bouncycastle.version>1.56</bouncycastle.version> | 76 | <bouncycastle.version>1.56</bouncycastle.version> |
75 | <winsw.version>2.0.1</winsw.version> | 77 | <winsw.version>2.0.1</winsw.version> |
76 | <hsqldb.version>2.4.0</hsqldb.version> | 78 | <hsqldb.version>2.4.0</hsqldb.version> |
@@ -85,6 +87,8 @@ | @@ -85,6 +87,8 @@ | ||
85 | <kafka.version>2.0.0</kafka.version> | 87 | <kafka.version>2.0.0</kafka.version> |
86 | <bucket4j.version>4.1.1</bucket4j.version> | 88 | <bucket4j.version>4.1.1</bucket4j.version> |
87 | <fst.version>2.57</fst.version> | 89 | <fst.version>2.57</fst.version> |
90 | + <antlr.version>2.7.7</antlr.version> | ||
91 | + <snakeyaml.version>1.23</snakeyaml.version> | ||
88 | </properties> | 92 | </properties> |
89 | 93 | ||
90 | <modules> | 94 | <modules> |
@@ -513,6 +517,16 @@ | @@ -513,6 +517,16 @@ | ||
513 | </exclusions> | 517 | </exclusions> |
514 | </dependency> | 518 | </dependency> |
515 | <dependency> | 519 | <dependency> |
520 | + <groupId>org.yaml</groupId> | ||
521 | + <artifactId>snakeyaml</artifactId> | ||
522 | + <version>${snakeyaml.version}</version> | ||
523 | + </dependency> | ||
524 | + <dependency> | ||
525 | + <groupId>antlr</groupId> | ||
526 | + <artifactId>antlr</artifactId> | ||
527 | + <version>${antlr.version}</version> | ||
528 | + </dependency> | ||
529 | + <dependency> | ||
516 | <groupId>com.rabbitmq</groupId> | 530 | <groupId>com.rabbitmq</groupId> |
517 | <artifactId>amqp-client</artifactId> | 531 | <artifactId>amqp-client</artifactId> |
518 | <version>${rabbitmq.version}</version> | 532 | <version>${rabbitmq.version}</version> |
@@ -601,6 +615,16 @@ | @@ -601,6 +615,16 @@ | ||
601 | <version>${jackson.version}</version> | 615 | <version>${jackson.version}</version> |
602 | </dependency> | 616 | </dependency> |
603 | <dependency> | 617 | <dependency> |
618 | + <groupId>com.fasterxml.jackson.core</groupId> | ||
619 | + <artifactId>jackson-core</artifactId> | ||
620 | + <version>${jackson.version}</version> | ||
621 | + </dependency> | ||
622 | + <dependency> | ||
623 | + <groupId>com.fasterxml.jackson.core</groupId> | ||
624 | + <artifactId>jackson-annotations</artifactId> | ||
625 | + <version>${jackson.version}</version> | ||
626 | + </dependency> | ||
627 | + <dependency> | ||
604 | <groupId>com.github.fge</groupId> | 628 | <groupId>com.github.fge</groupId> |
605 | <artifactId>json-schema-validator</artifactId> | 629 | <artifactId>json-schema-validator</artifactId> |
606 | <version>${json-schema-validator.version}</version> | 630 | <version>${json-schema-validator.version}</version> |
@@ -794,12 +818,28 @@ | @@ -794,12 +818,28 @@ | ||
794 | <groupId>de.ruedigermoeller</groupId> | 818 | <groupId>de.ruedigermoeller</groupId> |
795 | <artifactId>fst</artifactId> | 819 | <artifactId>fst</artifactId> |
796 | <version>${fst.version}</version> | 820 | <version>${fst.version}</version> |
821 | + <exclusions> | ||
822 | + <exclusion> | ||
823 | + <groupId>com.fasterxml.jackson.core</groupId> | ||
824 | + <artifactId>jackson-core</artifactId> | ||
825 | + </exclusion> | ||
826 | + </exclusions> | ||
797 | </dependency> | 827 | </dependency> |
798 | <dependency> | 828 | <dependency> |
799 | <groupId>io.springfox.ui</groupId> | 829 | <groupId>io.springfox.ui</groupId> |
800 | <artifactId>springfox-swagger-ui-rfc6570</artifactId> | 830 | <artifactId>springfox-swagger-ui-rfc6570</artifactId> |
801 | <version>${springfox-swagger-ui-rfc6570.version}</version> | 831 | <version>${springfox-swagger-ui-rfc6570.version}</version> |
802 | </dependency> | 832 | </dependency> |
833 | + <dependency> | ||
834 | + <groupId>org.locationtech.spatial4j</groupId> | ||
835 | + <artifactId>spatial4j</artifactId> | ||
836 | + <version>${spatial4j.version}</version> | ||
837 | + </dependency> | ||
838 | + <dependency> | ||
839 | + <groupId>org.locationtech.jts</groupId> | ||
840 | + <artifactId>jts-core</artifactId> | ||
841 | + <version>${jts.version}</version> | ||
842 | + </dependency> | ||
803 | </dependencies> | 843 | </dependencies> |
804 | </dependencyManagement> | 844 | </dependencyManagement> |
805 | 845 |
@@ -25,10 +25,12 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; | @@ -25,10 +25,12 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; | ||
25 | import org.thingsboard.server.dao.alarm.AlarmService; | 25 | import org.thingsboard.server.dao.alarm.AlarmService; |
26 | import org.thingsboard.server.dao.asset.AssetService; | 26 | import org.thingsboard.server.dao.asset.AssetService; |
27 | import org.thingsboard.server.dao.attributes.AttributesService; | 27 | import org.thingsboard.server.dao.attributes.AttributesService; |
28 | +import org.thingsboard.server.dao.cassandra.CassandraCluster; | ||
28 | import org.thingsboard.server.dao.customer.CustomerService; | 29 | import org.thingsboard.server.dao.customer.CustomerService; |
29 | import org.thingsboard.server.dao.dashboard.DashboardService; | 30 | import org.thingsboard.server.dao.dashboard.DashboardService; |
30 | import org.thingsboard.server.dao.device.DeviceService; | 31 | import org.thingsboard.server.dao.device.DeviceService; |
31 | import org.thingsboard.server.dao.entityview.EntityViewService; | 32 | import org.thingsboard.server.dao.entityview.EntityViewService; |
33 | +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; | ||
32 | import org.thingsboard.server.dao.relation.RelationService; | 34 | import org.thingsboard.server.dao.relation.RelationService; |
33 | import org.thingsboard.server.dao.rule.RuleChainService; | 35 | import org.thingsboard.server.dao.rule.RuleChainService; |
34 | import org.thingsboard.server.dao.tenant.TenantService; | 36 | import org.thingsboard.server.dao.tenant.TenantService; |
@@ -111,4 +113,9 @@ public interface TbContext { | @@ -111,4 +113,9 @@ public interface TbContext { | ||
111 | 113 | ||
112 | EventLoopGroup getSharedEventLoop(); | 114 | EventLoopGroup getSharedEventLoop(); |
113 | 115 | ||
116 | + CassandraCluster getCassandraCluster(); | ||
117 | + | ||
118 | + CassandraBufferedRateExecutor getCassandraBufferedRateExecutor(); | ||
119 | + | ||
120 | + | ||
114 | } | 121 | } |
@@ -98,6 +98,14 @@ | @@ -98,6 +98,14 @@ | ||
98 | <artifactId>bcpkix-jdk15on</artifactId> | 98 | <artifactId>bcpkix-jdk15on</artifactId> |
99 | </dependency> | 99 | </dependency> |
100 | <dependency> | 100 | <dependency> |
101 | + <groupId>org.locationtech.spatial4j</groupId> | ||
102 | + <artifactId>spatial4j</artifactId> | ||
103 | + </dependency> | ||
104 | + <dependency> | ||
105 | + <groupId>org.locationtech.jts</groupId> | ||
106 | + <artifactId>jts-core</artifactId> | ||
107 | + </dependency> | ||
108 | + <dependency> | ||
101 | <groupId>junit</groupId> | 109 | <groupId>junit</groupId> |
102 | <artifactId>junit</artifactId> | 110 | <artifactId>junit</artifactId> |
103 | <version>${junit.version}</version> | 111 | <version>${junit.version}</version> |
@@ -142,10 +150,10 @@ | @@ -142,10 +150,10 @@ | ||
142 | <executable>true</executable> | 150 | <executable>true</executable> |
143 | <excludeDevtools>true</excludeDevtools> | 151 | <excludeDevtools>true</excludeDevtools> |
144 | <!--<embeddedLaunchScriptProperties>--> | 152 | <!--<embeddedLaunchScriptProperties>--> |
145 | - <!--<confFolder>${pkg.installFolder}/conf</confFolder>--> | ||
146 | - <!--<logFolder>${pkg.unixLogFolder}</logFolder>--> | ||
147 | - <!--<logFilename>${pkg.name}.out</logFilename>--> | ||
148 | - <!--<initInfoProvides>${pkg.name}</initInfoProvides>--> | 153 | + <!--<confFolder>${pkg.installFolder}/conf</confFolder>--> |
154 | + <!--<logFolder>${pkg.unixLogFolder}</logFolder>--> | ||
155 | + <!--<logFilename>${pkg.name}.out</logFilename>--> | ||
156 | + <!--<initInfoProvides>${pkg.name}</initInfoProvides>--> | ||
149 | <!--</embeddedLaunchScriptProperties>--> | 157 | <!--</embeddedLaunchScriptProperties>--> |
150 | </configuration> | 158 | </configuration> |
151 | <executions> | 159 | <executions> |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.action; | ||
17 | + | ||
18 | +import com.datastax.driver.core.BoundStatement; | ||
19 | +import com.datastax.driver.core.CodecRegistry; | ||
20 | +import com.datastax.driver.core.ConsistencyLevel; | ||
21 | +import com.datastax.driver.core.PreparedStatement; | ||
22 | +import com.datastax.driver.core.ResultSet; | ||
23 | +import com.datastax.driver.core.ResultSetFuture; | ||
24 | +import com.datastax.driver.core.Session; | ||
25 | +import com.datastax.driver.core.Statement; | ||
26 | +import com.datastax.driver.core.TypeCodec; | ||
27 | +import com.datastax.driver.core.exceptions.CodecNotFoundException; | ||
28 | +import com.google.common.base.Function; | ||
29 | +import com.google.common.util.concurrent.Futures; | ||
30 | +import com.google.common.util.concurrent.ListenableFuture; | ||
31 | +import com.google.gson.JsonElement; | ||
32 | +import com.google.gson.JsonObject; | ||
33 | +import com.google.gson.JsonParser; | ||
34 | +import com.google.gson.JsonPrimitive; | ||
35 | +import lombok.extern.slf4j.Slf4j; | ||
36 | +import org.thingsboard.rule.engine.api.RuleNode; | ||
37 | +import org.thingsboard.rule.engine.api.TbContext; | ||
38 | +import org.thingsboard.rule.engine.api.TbNode; | ||
39 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
40 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
41 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
42 | +import org.thingsboard.server.common.data.plugin.ComponentType; | ||
43 | +import org.thingsboard.server.common.msg.TbMsg; | ||
44 | +import org.thingsboard.server.dao.cassandra.CassandraCluster; | ||
45 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
46 | +import org.thingsboard.server.dao.model.type.AuthorityCodec; | ||
47 | +import org.thingsboard.server.dao.model.type.ComponentLifecycleStateCodec; | ||
48 | +import org.thingsboard.server.dao.model.type.ComponentScopeCodec; | ||
49 | +import org.thingsboard.server.dao.model.type.ComponentTypeCodec; | ||
50 | +import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec; | ||
51 | +import org.thingsboard.server.dao.model.type.EntityTypeCodec; | ||
52 | +import org.thingsboard.server.dao.model.type.JsonCodec; | ||
53 | +import org.thingsboard.server.dao.nosql.CassandraStatementTask; | ||
54 | + | ||
55 | +import javax.annotation.Nullable; | ||
56 | +import java.util.ArrayList; | ||
57 | +import java.util.List; | ||
58 | +import java.util.Map; | ||
59 | +import java.util.concurrent.ExecutionException; | ||
60 | +import java.util.concurrent.ExecutorService; | ||
61 | +import java.util.concurrent.Executors; | ||
62 | +import java.util.concurrent.atomic.AtomicInteger; | ||
63 | + | ||
64 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | ||
65 | +import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback; | ||
66 | + | ||
67 | +@Slf4j | ||
68 | +@RuleNode(type = ComponentType.ACTION, | ||
69 | + name = "save to custom table", | ||
70 | + configClazz = TbSaveToCustomCassandraTableNodeConfiguration.class, | ||
71 | + nodeDescription = "Node stores data from incoming Message payload to the Cassandra database into the predefined custom table" + | ||
72 | + " that should have <b>cs_tb_</b> prefix, to avoid the data insertion to the common TB tables.<br>" + | ||
73 | + "<b>Note:</b> rule node can be used only for Cassandra DB.", | ||
74 | + nodeDetails = "Administrator should set the custom table name without prefix: <b>cs_tb_</b>. <br>" + | ||
75 | + "Administrator can configure the mapping between the Message field names and Table columns name.<br>" + | ||
76 | + "<b>Note:</b>If the mapping key is <b>$entity_id</b>, that is identified by the Message Originator, then to the appropriate column name(mapping value) will be write the message originator id.<br><br>" + | ||
77 | + "If specified message field does not exist or is not a JSON Primitive, the outbound message will be routed via <b>failure</b> chain," + | ||
78 | + " otherwise, the message will be routed via <b>success</b> chain.", | ||
79 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | ||
80 | + configDirective = "tbActionNodeCustomTableConfig", | ||
81 | + icon = "file_upload") | ||
82 | +public class TbSaveToCustomCassandraTableNode implements TbNode { | ||
83 | + | ||
84 | + private static final String TABLE_PREFIX = "cs_tb_"; | ||
85 | + private static final JsonParser parser = new JsonParser(); | ||
86 | + private static final String ENTITY_ID = "$entityId"; | ||
87 | + | ||
88 | + private TbSaveToCustomCassandraTableNodeConfiguration config; | ||
89 | + private Session session; | ||
90 | + private CassandraCluster cassandraCluster; | ||
91 | + private ConsistencyLevel defaultWriteLevel; | ||
92 | + private PreparedStatement saveStmt; | ||
93 | + private ExecutorService readResultsProcessingExecutor; | ||
94 | + private Map<String, String> fieldsMap; | ||
95 | + | ||
96 | + @Override | ||
97 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | ||
98 | + config = TbNodeUtils.convert(configuration, TbSaveToCustomCassandraTableNodeConfiguration.class); | ||
99 | + cassandraCluster = ctx.getCassandraCluster(); | ||
100 | + if (cassandraCluster == null) { | ||
101 | + throw new RuntimeException("Unable to connect to Cassandra database"); | ||
102 | + } else { | ||
103 | + startExecutor(); | ||
104 | + saveStmt = getSaveStmt(); | ||
105 | + } | ||
106 | + } | ||
107 | + | ||
108 | + @Override | ||
109 | + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { | ||
110 | + withCallback(save(msg, ctx), aVoid -> { | ||
111 | + ctx.tellNext(msg, SUCCESS); | ||
112 | + }, e -> ctx.tellFailure(msg, e), ctx.getDbCallbackExecutor()); | ||
113 | + } | ||
114 | + | ||
115 | + @Override | ||
116 | + public void destroy() { | ||
117 | + stopExecutor(); | ||
118 | + saveStmt = null; | ||
119 | + } | ||
120 | + | ||
121 | + private void startExecutor() { | ||
122 | + readResultsProcessingExecutor = Executors.newCachedThreadPool(); | ||
123 | + } | ||
124 | + | ||
125 | + private void stopExecutor() { | ||
126 | + if (readResultsProcessingExecutor != null) { | ||
127 | + readResultsProcessingExecutor.shutdownNow(); | ||
128 | + } | ||
129 | + } | ||
130 | + | ||
131 | + private PreparedStatement prepare(String query) { | ||
132 | + return getSession().prepare(query); | ||
133 | + } | ||
134 | + | ||
135 | + private Session getSession() { | ||
136 | + if (session == null) { | ||
137 | + session = cassandraCluster.getSession(); | ||
138 | + defaultWriteLevel = cassandraCluster.getDefaultWriteConsistencyLevel(); | ||
139 | + CodecRegistry registry = session.getCluster().getConfiguration().getCodecRegistry(); | ||
140 | + registerCodecIfNotFound(registry, new JsonCodec()); | ||
141 | + registerCodecIfNotFound(registry, new DeviceCredentialsTypeCodec()); | ||
142 | + registerCodecIfNotFound(registry, new AuthorityCodec()); | ||
143 | + registerCodecIfNotFound(registry, new ComponentLifecycleStateCodec()); | ||
144 | + registerCodecIfNotFound(registry, new ComponentTypeCodec()); | ||
145 | + registerCodecIfNotFound(registry, new ComponentScopeCodec()); | ||
146 | + registerCodecIfNotFound(registry, new EntityTypeCodec()); | ||
147 | + } | ||
148 | + return session; | ||
149 | + } | ||
150 | + | ||
151 | + private void registerCodecIfNotFound(CodecRegistry registry, TypeCodec<?> codec) { | ||
152 | + try { | ||
153 | + registry.codecFor(codec.getCqlType(), codec.getJavaType()); | ||
154 | + } catch (CodecNotFoundException e) { | ||
155 | + registry.register(codec); | ||
156 | + } | ||
157 | + } | ||
158 | + | ||
159 | + | ||
160 | + private PreparedStatement getSaveStmt() { | ||
161 | + fieldsMap = config.getFieldsMapping(); | ||
162 | + if (fieldsMap.isEmpty()) { | ||
163 | + throw new RuntimeException("Fields(key,value) map is empty!"); | ||
164 | + } else { | ||
165 | + return prepareStatement(new ArrayList<>(fieldsMap.values())); | ||
166 | + } | ||
167 | + } | ||
168 | + | ||
169 | + private PreparedStatement prepareStatement(List<String> fieldsList) { | ||
170 | + return prepare(createQuery(fieldsList)); | ||
171 | + } | ||
172 | + | ||
173 | + private String createQuery(List<String> fieldsList) { | ||
174 | + int size = fieldsList.size(); | ||
175 | + StringBuilder query = new StringBuilder(); | ||
176 | + query.append("INSERT INTO ") | ||
177 | + .append(TABLE_PREFIX) | ||
178 | + .append(config.getTableName()) | ||
179 | + .append("("); | ||
180 | + for (String field : fieldsList) { | ||
181 | + query.append(field); | ||
182 | + if (fieldsList.get(size - 1).equals(field)) { | ||
183 | + query.append(")"); | ||
184 | + } else { | ||
185 | + query.append(","); | ||
186 | + } | ||
187 | + } | ||
188 | + query.append(" VALUES("); | ||
189 | + for (int i = 0; i < size; i++) { | ||
190 | + if (i == size - 1) { | ||
191 | + query.append("?)"); | ||
192 | + } else { | ||
193 | + query.append("?, "); | ||
194 | + } | ||
195 | + } | ||
196 | + return query.toString(); | ||
197 | + } | ||
198 | + | ||
199 | + private ListenableFuture<Void> save(TbMsg msg, TbContext ctx) { | ||
200 | + JsonElement data = parser.parse(msg.getData()); | ||
201 | + if (!data.isJsonObject()) { | ||
202 | + throw new IllegalStateException("Invalid message structure, it is not a JSON Object:" + data); | ||
203 | + } else { | ||
204 | + JsonObject dataAsObject = data.getAsJsonObject(); | ||
205 | + BoundStatement stmt = saveStmt.bind(); | ||
206 | + AtomicInteger i = new AtomicInteger(0); | ||
207 | + fieldsMap.forEach((key, value) -> { | ||
208 | + if (key.equals(ENTITY_ID)) { | ||
209 | + stmt.setUUID(i.get(), msg.getOriginator().getId()); | ||
210 | + } else if (dataAsObject.has(key)) { | ||
211 | + if (dataAsObject.get(key).isJsonPrimitive()) { | ||
212 | + JsonPrimitive primitive = dataAsObject.get(key).getAsJsonPrimitive(); | ||
213 | + if (primitive.isNumber()) { | ||
214 | + stmt.setLong(i.get(), dataAsObject.get(key).getAsLong()); | ||
215 | + } else if (primitive.isBoolean()) { | ||
216 | + stmt.setBool(i.get(), dataAsObject.get(key).getAsBoolean()); | ||
217 | + } else if (primitive.isString()) { | ||
218 | + stmt.setString(i.get(), dataAsObject.get(key).getAsString()); | ||
219 | + } else { | ||
220 | + stmt.setToNull(i.get()); | ||
221 | + } | ||
222 | + } else { | ||
223 | + throw new IllegalStateException("Message data key: '" + key + "' with value: '" + value + "' is not a JSON Primitive!"); | ||
224 | + } | ||
225 | + } else { | ||
226 | + throw new RuntimeException("Message data doesn't contain key: " + "'" + key + "'!"); | ||
227 | + } | ||
228 | + i.getAndIncrement(); | ||
229 | + }); | ||
230 | + return getFuture(executeAsyncWrite(ctx, stmt), rs -> null); | ||
231 | + } | ||
232 | + } | ||
233 | + | ||
234 | + private ResultSetFuture executeAsyncWrite(TbContext ctx, Statement statement) { | ||
235 | + return executeAsync(ctx, statement, defaultWriteLevel); | ||
236 | + } | ||
237 | + | ||
238 | + private ResultSetFuture executeAsync(TbContext ctx, Statement statement, ConsistencyLevel level) { | ||
239 | + if (log.isDebugEnabled()) { | ||
240 | + log.debug("Execute cassandra async statement {}", statementToString(statement)); | ||
241 | + } | ||
242 | + if (statement.getConsistencyLevel() == null) { | ||
243 | + statement.setConsistencyLevel(level); | ||
244 | + } | ||
245 | + return ctx.getCassandraBufferedRateExecutor().submit(new CassandraStatementTask(ctx.getTenantId(), getSession(), statement)); | ||
246 | + } | ||
247 | + | ||
248 | + private static String statementToString(Statement statement) { | ||
249 | + if (statement instanceof BoundStatement) { | ||
250 | + return ((BoundStatement) statement).preparedStatement().getQueryString(); | ||
251 | + } else { | ||
252 | + return statement.toString(); | ||
253 | + } | ||
254 | + } | ||
255 | + | ||
256 | + private <T> ListenableFuture<T> getFuture(ResultSetFuture future, java.util.function.Function<ResultSet, T> transformer) { | ||
257 | + return Futures.transform(future, new Function<ResultSet, T>() { | ||
258 | + @Nullable | ||
259 | + @Override | ||
260 | + public T apply(@Nullable ResultSet input) { | ||
261 | + return transformer.apply(input); | ||
262 | + } | ||
263 | + }, readResultsProcessingExecutor); | ||
264 | + } | ||
265 | + | ||
266 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.action; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | ||
20 | + | ||
21 | +import java.util.HashMap; | ||
22 | +import java.util.Map; | ||
23 | + | ||
24 | +@Data | ||
25 | +public class TbSaveToCustomCassandraTableNodeConfiguration implements NodeConfiguration<TbSaveToCustomCassandraTableNodeConfiguration> { | ||
26 | + | ||
27 | + | ||
28 | + private String tableName; | ||
29 | + private Map<String, String> fieldsMapping; | ||
30 | + | ||
31 | + | ||
32 | + @Override | ||
33 | + public TbSaveToCustomCassandraTableNodeConfiguration defaultConfiguration() { | ||
34 | + TbSaveToCustomCassandraTableNodeConfiguration configuration = new TbSaveToCustomCassandraTableNodeConfiguration(); | ||
35 | + configuration.setTableName(""); | ||
36 | + Map<String, String> map = new HashMap<>(); | ||
37 | + map.put("", ""); | ||
38 | + configuration.setFieldsMapping(map); | ||
39 | + return configuration; | ||
40 | + } | ||
41 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import com.google.gson.JsonElement; | ||
19 | +import com.google.gson.JsonObject; | ||
20 | +import com.google.gson.JsonParser; | ||
21 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | ||
22 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | ||
23 | +import org.springframework.util.StringUtils; | ||
24 | +import org.thingsboard.rule.engine.api.TbContext; | ||
25 | +import org.thingsboard.rule.engine.api.TbNode; | ||
26 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
27 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
28 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
29 | +import org.thingsboard.server.common.msg.TbMsg; | ||
30 | + | ||
31 | +import java.util.Collections; | ||
32 | +import java.util.List; | ||
33 | + | ||
34 | +public abstract class AbstractGeofencingNode<T extends TbGpsGeofencingFilterNodeConfiguration> implements TbNode { | ||
35 | + | ||
36 | + protected T config; | ||
37 | + protected JtsSpatialContext jtsCtx; | ||
38 | + | ||
39 | + @Override | ||
40 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | ||
41 | + this.config = TbNodeUtils.convert(configuration, getConfigClazz()); | ||
42 | + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); | ||
43 | + factory.normWrapLongitude = true; | ||
44 | + jtsCtx = factory.newSpatialContext(); | ||
45 | + } | ||
46 | + | ||
47 | + abstract protected Class<T> getConfigClazz(); | ||
48 | + | ||
49 | + protected boolean checkMatches(TbMsg msg) throws TbNodeException { | ||
50 | + JsonElement msgDataElement = new JsonParser().parse(msg.getData()); | ||
51 | + if (!msgDataElement.isJsonObject()) { | ||
52 | + throw new TbNodeException("Incoming Message is not a valid JSON object"); | ||
53 | + } | ||
54 | + JsonObject msgDataObj = msgDataElement.getAsJsonObject(); | ||
55 | + double latitude = getValueFromMessageByName(msg, msgDataObj, config.getLatitudeKeyName()); | ||
56 | + double longitude = getValueFromMessageByName(msg, msgDataObj, config.getLongitudeKeyName()); | ||
57 | + List<Perimeter> perimeters = getPerimeters(msg, msgDataObj); | ||
58 | + boolean matches = false; | ||
59 | + for (Perimeter perimeter : perimeters) { | ||
60 | + if (checkMatches(perimeter, latitude, longitude)) { | ||
61 | + matches = true; | ||
62 | + break; | ||
63 | + } | ||
64 | + } | ||
65 | + return matches; | ||
66 | + } | ||
67 | + | ||
68 | + protected boolean checkMatches(Perimeter perimeter, double latitude, double longitude) throws TbNodeException { | ||
69 | + if (perimeter.getPerimeterType() == PerimeterType.CIRCLE) { | ||
70 | + Coordinates entityCoordinates = new Coordinates(latitude, longitude); | ||
71 | + Coordinates perimeterCoordinates = new Coordinates(perimeter.getCenterLatitude(), perimeter.getCenterLongitude()); | ||
72 | + return perimeter.getRange() > GeoUtil.distance(entityCoordinates, perimeterCoordinates, perimeter.getRangeUnit()); | ||
73 | + } else if (perimeter.getPerimeterType() == PerimeterType.POLYGON) { | ||
74 | + return GeoUtil.contains(perimeter.getPolygonsDefinition(), new Coordinates(latitude, longitude)); | ||
75 | + } else { | ||
76 | + throw new TbNodeException("Unsupported perimeter type: " + perimeter.getPerimeterType()); | ||
77 | + } | ||
78 | + } | ||
79 | + | ||
80 | + protected List<Perimeter> getPerimeters(TbMsg msg, JsonObject msgDataObj) throws TbNodeException { | ||
81 | + if (config.isFetchPerimeterInfoFromMessageMetadata()) { | ||
82 | + //TODO: add fetching perimeters from the message itself, if configuration is empty. | ||
83 | + if (!StringUtils.isEmpty(msg.getMetaData().getValue("perimeter"))) { | ||
84 | + Perimeter perimeter = new Perimeter(); | ||
85 | + perimeter.setPerimeterType(PerimeterType.POLYGON); | ||
86 | + perimeter.setPolygonsDefinition(msg.getMetaData().getValue("perimeter")); | ||
87 | + return Collections.singletonList(perimeter); | ||
88 | + } else if (!StringUtils.isEmpty(msg.getMetaData().getValue("centerLatitude"))) { | ||
89 | + Perimeter perimeter = new Perimeter(); | ||
90 | + perimeter.setPerimeterType(PerimeterType.CIRCLE); | ||
91 | + perimeter.setCenterLatitude(Double.parseDouble(msg.getMetaData().getValue("centerLatitude"))); | ||
92 | + perimeter.setCenterLongitude(Double.parseDouble(msg.getMetaData().getValue("centerLongitude"))); | ||
93 | + perimeter.setRange(Double.parseDouble(msg.getMetaData().getValue("range"))); | ||
94 | + perimeter.setRangeUnit(RangeUnit.valueOf(msg.getMetaData().getValue("rangeUnit"))); | ||
95 | + return Collections.singletonList(perimeter); | ||
96 | + } else { | ||
97 | + throw new TbNodeException("Missing perimeter definition!"); | ||
98 | + } | ||
99 | + } else { | ||
100 | + Perimeter perimeter = new Perimeter(); | ||
101 | + perimeter.setPerimeterType(config.getPerimeterType()); | ||
102 | + perimeter.setCenterLatitude(config.getCenterLatitude()); | ||
103 | + perimeter.setCenterLongitude(config.getCenterLongitude()); | ||
104 | + perimeter.setRange(config.getRange()); | ||
105 | + perimeter.setRangeUnit(config.getRangeUnit()); | ||
106 | + perimeter.setPolygonsDefinition(config.getPolygonsDefinition()); | ||
107 | + return Collections.singletonList(perimeter); | ||
108 | + } | ||
109 | + } | ||
110 | + | ||
111 | + protected Double getValueFromMessageByName(TbMsg msg, JsonObject msgDataObj, String keyName) throws TbNodeException { | ||
112 | + double value; | ||
113 | + if (msgDataObj.has(keyName) && msgDataObj.get(keyName).isJsonPrimitive()) { | ||
114 | + value = msgDataObj.get(keyName).getAsDouble(); | ||
115 | + } else { | ||
116 | + String valueStr = msg.getMetaData().getValue(keyName); | ||
117 | + if (!StringUtils.isEmpty(valueStr)) { | ||
118 | + value = Double.parseDouble(valueStr); | ||
119 | + } else { | ||
120 | + throw new TbNodeException("Incoming Message has no " + keyName + " in data or metadata!"); | ||
121 | + } | ||
122 | + } | ||
123 | + return value; | ||
124 | + } | ||
125 | + | ||
126 | + @Override | ||
127 | + public void destroy() { | ||
128 | + | ||
129 | + } | ||
130 | + | ||
131 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Coordinates.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +@Data | ||
21 | +public class Coordinates { | ||
22 | + private final double latitude; | ||
23 | + private final double longitude; | ||
24 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import lombok.AllArgsConstructor; | ||
19 | +import lombok.Data; | ||
20 | + | ||
21 | +@Data | ||
22 | +@AllArgsConstructor | ||
23 | +public class EntityGeofencingState { | ||
24 | + | ||
25 | + private boolean inside; | ||
26 | + private long stateSwitchTime; | ||
27 | + private boolean stayed; | ||
28 | + | ||
29 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import com.google.gson.JsonArray; | ||
19 | +import com.google.gson.JsonElement; | ||
20 | +import com.google.gson.JsonParser; | ||
21 | +import org.locationtech.spatial4j.context.SpatialContext; | ||
22 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | ||
23 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | ||
24 | +import org.locationtech.spatial4j.distance.DistanceUtils; | ||
25 | +import org.locationtech.spatial4j.shape.Point; | ||
26 | +import org.locationtech.spatial4j.shape.Shape; | ||
27 | +import org.locationtech.spatial4j.shape.ShapeFactory; | ||
28 | +import org.locationtech.spatial4j.shape.SpatialRelation; | ||
29 | + | ||
30 | +public class GeoUtil { | ||
31 | + | ||
32 | + private static final SpatialContext distCtx = SpatialContext.GEO; | ||
33 | + private static final JtsSpatialContext jtsCtx; | ||
34 | + | ||
35 | + static { | ||
36 | + JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); | ||
37 | + factory.normWrapLongitude = true; | ||
38 | + jtsCtx = factory.newSpatialContext(); | ||
39 | + } | ||
40 | + | ||
41 | + public static synchronized double distance(Coordinates x, Coordinates y, RangeUnit unit) { | ||
42 | + Point xLL = distCtx.getShapeFactory().pointXY(x.getLongitude(), x.getLatitude()); | ||
43 | + Point yLL = distCtx.getShapeFactory().pointXY(y.getLongitude(), y.getLatitude()); | ||
44 | + return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM); | ||
45 | + } | ||
46 | + | ||
47 | + public static synchronized boolean contains(String polygon, Coordinates coordinates) { | ||
48 | + ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon(); | ||
49 | + JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray(); | ||
50 | + boolean first = true; | ||
51 | + double firstLat = 0.0; | ||
52 | + double firstLng = 0.0; | ||
53 | + for (JsonElement jsonElement : polygonArray) { | ||
54 | + double lat = jsonElement.getAsJsonArray().get(0).getAsDouble(); | ||
55 | + double lng = jsonElement.getAsJsonArray().get(1).getAsDouble(); | ||
56 | + if (first) { | ||
57 | + firstLat = lat; | ||
58 | + firstLng = lng; | ||
59 | + first = false; | ||
60 | + } | ||
61 | + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(lng), jtsCtx.getShapeFactory().normY(lat)); | ||
62 | + } | ||
63 | + polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normY(firstLat)); | ||
64 | + Shape shape = polygonBuilder.buildOrRect(); | ||
65 | + Point point = jtsCtx.makePoint(coordinates.getLongitude(), coordinates.getLatitude()); | ||
66 | + return shape.relate(point).equals(SpatialRelation.CONTAINS); | ||
67 | + } | ||
68 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/Perimeter.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | + | ||
20 | +@Data | ||
21 | +public class Perimeter { | ||
22 | + | ||
23 | + private PerimeterType perimeterType; | ||
24 | + | ||
25 | + //For Polygons | ||
26 | + private String polygonsDefinition; | ||
27 | + | ||
28 | + //For Circles | ||
29 | + private Double centerLatitude; | ||
30 | + private Double centerLongitude; | ||
31 | + private Double range; | ||
32 | + private RangeUnit rangeUnit; | ||
33 | + | ||
34 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/PerimeterType.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +public enum PerimeterType { | ||
19 | + CIRCLE, POLYGON | ||
20 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/RangeUnit.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +public enum RangeUnit { | ||
19 | + METER(1000.0), KILOMETER(1.0), FOOT(3280.84), MILE(0.62137), NAUTICAL_MILE(0.539957); | ||
20 | + | ||
21 | + private final double fromKm; | ||
22 | + | ||
23 | + RangeUnit(double fromKm) { | ||
24 | + this.fromKm = fromKm; | ||
25 | + } | ||
26 | + | ||
27 | + public double fromKm(double v) { | ||
28 | + return v * fromKm; | ||
29 | + } | ||
30 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import com.google.gson.Gson; | ||
19 | +import com.google.gson.JsonObject; | ||
20 | +import com.google.gson.JsonParser; | ||
21 | +import lombok.extern.slf4j.Slf4j; | ||
22 | +import org.thingsboard.rule.engine.api.RuleNode; | ||
23 | +import org.thingsboard.rule.engine.api.TbContext; | ||
24 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
25 | +import org.thingsboard.server.common.data.DataConstants; | ||
26 | +import org.thingsboard.server.common.data.id.EntityId; | ||
27 | +import org.thingsboard.server.common.data.kv.AttributeKvEntry; | ||
28 | +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; | ||
29 | +import org.thingsboard.server.common.data.kv.StringDataEntry; | ||
30 | +import org.thingsboard.server.common.data.plugin.ComponentType; | ||
31 | +import org.thingsboard.server.common.msg.TbMsg; | ||
32 | + | ||
33 | +import java.util.Collections; | ||
34 | +import java.util.HashMap; | ||
35 | +import java.util.List; | ||
36 | +import java.util.Map; | ||
37 | +import java.util.Optional; | ||
38 | +import java.util.concurrent.ExecutionException; | ||
39 | +import java.util.concurrent.TimeUnit; | ||
40 | +import java.util.concurrent.TimeoutException; | ||
41 | + | ||
42 | +/** | ||
43 | + * Created by ashvayka on 19.01.18. | ||
44 | + */ | ||
45 | +@Slf4j | ||
46 | +@RuleNode( | ||
47 | + type = ComponentType.ACTION, | ||
48 | + name = "gps geofencing events", | ||
49 | + configClazz = TbGpsGeofencingActionNodeConfiguration.class, | ||
50 | + relationTypes = {"Entered", "Left", "Inside", "Outside"}, | ||
51 | + nodeDescription = "Produces incoming messages using GPS based geofencing", | ||
52 | + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns different events based on configuration parameters", | ||
53 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | ||
54 | + configDirective = "tbActionNodeGpsGeofencingConfig") | ||
55 | +public class TbGpsGeofencingActionNode extends AbstractGeofencingNode<TbGpsGeofencingActionNodeConfiguration> { | ||
56 | + | ||
57 | + private final Map<EntityId, EntityGeofencingState> entityStates = new HashMap<>(); | ||
58 | + private final Gson gson = new Gson(); | ||
59 | + private final JsonParser parser = new JsonParser(); | ||
60 | + | ||
61 | + @Override | ||
62 | + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | ||
63 | + boolean matches = checkMatches(msg); | ||
64 | + long ts = System.currentTimeMillis(); | ||
65 | + | ||
66 | + EntityGeofencingState entityState = entityStates.computeIfAbsent(msg.getOriginator(), key -> { | ||
67 | + try { | ||
68 | + Optional<AttributeKvEntry> entry = ctx.getAttributesService() | ||
69 | + .find(ctx.getTenantId(), msg.getOriginator(), DataConstants.SERVER_SCOPE, ctx.getNodeId()) | ||
70 | + .get(1, TimeUnit.MINUTES); | ||
71 | + if (entry.isPresent()) { | ||
72 | + JsonObject element = parser.parse(entry.get().getValueAsString()).getAsJsonObject(); | ||
73 | + return new EntityGeofencingState(element.get("inside").getAsBoolean(), element.get("stateSwitchTime").getAsLong(), element.get("stayed").getAsBoolean()); | ||
74 | + } else { | ||
75 | + return new EntityGeofencingState(false, 0L, false); | ||
76 | + } | ||
77 | + } catch (InterruptedException | TimeoutException | ExecutionException e) { | ||
78 | + throw new RuntimeException(e); | ||
79 | + } | ||
80 | + }); | ||
81 | + if (entityState.getStateSwitchTime() == 0L || entityState.isInside() != matches) { | ||
82 | + switchState(ctx, msg.getOriginator(), entityState, matches, ts); | ||
83 | + ctx.tellNext(msg, matches ? "Entered" : "Left"); | ||
84 | + } else if (!entityState.isStayed()) { | ||
85 | + long stayTime = ts - entityState.getStateSwitchTime(); | ||
86 | + if (stayTime > (entityState.isInside() ? | ||
87 | + TimeUnit.valueOf(config.getMinInsideDurationTimeUnit()).toMillis(config.getMinInsideDuration()) : TimeUnit.valueOf(config.getMinOutsideDurationTimeUnit()).toMillis(config.getMinOutsideDuration()))) { | ||
88 | + setStaid(ctx, msg.getOriginator(), entityState); | ||
89 | + ctx.tellNext(msg, entityState.isInside() ? "Inside" : "Outside"); | ||
90 | + } | ||
91 | + } | ||
92 | + } | ||
93 | + | ||
94 | + private void switchState(TbContext ctx, EntityId entityId, EntityGeofencingState entityState, boolean matches, long ts) { | ||
95 | + entityState.setInside(matches); | ||
96 | + entityState.setStateSwitchTime(ts); | ||
97 | + entityState.setStayed(false); | ||
98 | + persist(ctx, entityId, entityState); | ||
99 | + } | ||
100 | + | ||
101 | + private void setStaid(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { | ||
102 | + entityState.setStayed(true); | ||
103 | + persist(ctx, entityId, entityState); | ||
104 | + } | ||
105 | + | ||
106 | + private void persist(TbContext ctx, EntityId entityId, EntityGeofencingState entityState) { | ||
107 | + JsonObject object = new JsonObject(); | ||
108 | + object.addProperty("inside", entityState.isInside()); | ||
109 | + object.addProperty("stateSwitchTime", entityState.getStateSwitchTime()); | ||
110 | + object.addProperty("stayed", entityState.isStayed()); | ||
111 | + AttributeKvEntry entry = new BaseAttributeKvEntry(new StringDataEntry(ctx.getNodeId(), gson.toJson(object)), System.currentTimeMillis()); | ||
112 | + List<AttributeKvEntry> attributeKvEntryList = Collections.singletonList(entry); | ||
113 | + ctx.getAttributesService().save(ctx.getTenantId(), entityId, DataConstants.SERVER_SCOPE, attributeKvEntryList); | ||
114 | + } | ||
115 | + | ||
116 | + @Override | ||
117 | + protected Class<TbGpsGeofencingActionNodeConfiguration> getConfigClazz() { | ||
118 | + return TbGpsGeofencingActionNodeConfiguration.class; | ||
119 | + } | ||
120 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | ||
20 | + | ||
21 | +import java.util.Collections; | ||
22 | +import java.util.List; | ||
23 | +import java.util.concurrent.TimeUnit; | ||
24 | + | ||
25 | +/** | ||
26 | + * Created by ashvayka on 19.01.18. | ||
27 | + */ | ||
28 | +@Data | ||
29 | +public class TbGpsGeofencingActionNodeConfiguration extends TbGpsGeofencingFilterNodeConfiguration { | ||
30 | + | ||
31 | + private int minInsideDuration; | ||
32 | + private int minOutsideDuration; | ||
33 | + | ||
34 | + private String minInsideDurationTimeUnit; | ||
35 | + private String minOutsideDurationTimeUnit; | ||
36 | + | ||
37 | + @Override | ||
38 | + public TbGpsGeofencingActionNodeConfiguration defaultConfiguration() { | ||
39 | + TbGpsGeofencingActionNodeConfiguration configuration = new TbGpsGeofencingActionNodeConfiguration(); | ||
40 | + configuration.setLatitudeKeyName("latitude"); | ||
41 | + configuration.setLongitudeKeyName("longitude"); | ||
42 | + configuration.setFetchPerimeterInfoFromMessageMetadata(true); | ||
43 | + configuration.setMinInsideDurationTimeUnit(TimeUnit.MINUTES.name()); | ||
44 | + configuration.setMinOutsideDurationTimeUnit(TimeUnit.MINUTES.name()); | ||
45 | + configuration.setMinInsideDuration(1); | ||
46 | + configuration.setMinOutsideDuration(1); | ||
47 | + return configuration; | ||
48 | + } | ||
49 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import com.google.gson.JsonArray; | ||
19 | +import com.google.gson.JsonElement; | ||
20 | +import com.google.gson.JsonObject; | ||
21 | +import com.google.gson.JsonParser; | ||
22 | +import lombok.extern.slf4j.Slf4j; | ||
23 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContext; | ||
24 | +import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; | ||
25 | +import org.locationtech.spatial4j.shape.Point; | ||
26 | +import org.locationtech.spatial4j.shape.Shape; | ||
27 | +import org.locationtech.spatial4j.shape.ShapeFactory; | ||
28 | +import org.locationtech.spatial4j.shape.SpatialRelation; | ||
29 | +import org.springframework.util.StringUtils; | ||
30 | +import org.thingsboard.rule.engine.api.RuleNode; | ||
31 | +import org.thingsboard.rule.engine.api.TbContext; | ||
32 | +import org.thingsboard.rule.engine.api.TbNode; | ||
33 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
34 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
35 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
36 | +import org.thingsboard.rule.engine.filter.TbMsgTypeFilterNodeConfiguration; | ||
37 | +import org.thingsboard.server.common.data.plugin.ComponentType; | ||
38 | +import org.thingsboard.server.common.msg.TbMsg; | ||
39 | + | ||
40 | +import java.util.Collections; | ||
41 | +import java.util.List; | ||
42 | + | ||
43 | +/** | ||
44 | + * Created by ashvayka on 19.01.18. | ||
45 | + */ | ||
46 | +@Slf4j | ||
47 | +@RuleNode( | ||
48 | + type = ComponentType.FILTER, | ||
49 | + name = "gps geofencing filter", | ||
50 | + configClazz = TbGpsGeofencingFilterNodeConfiguration.class, | ||
51 | + relationTypes = {"True", "False"}, | ||
52 | + nodeDescription = "Filter incoming messages by GPS based geofencing", | ||
53 | + nodeDetails = "Extracts latitude and longitude parameters from incoming message and returns 'True' if they are inside configured perimeters, 'False' otherwise.", | ||
54 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | ||
55 | + configDirective = "tbFilterNodeGpsGeofencingConfig") | ||
56 | +public class TbGpsGeofencingFilterNode extends AbstractGeofencingNode<TbGpsGeofencingFilterNodeConfiguration> { | ||
57 | + | ||
58 | + @Override | ||
59 | + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { | ||
60 | + ctx.tellNext(msg, checkMatches(msg) ? "True" : "False"); | ||
61 | + } | ||
62 | + | ||
63 | + @Override | ||
64 | + protected Class<TbGpsGeofencingFilterNodeConfiguration> getConfigClazz() { | ||
65 | + return TbGpsGeofencingFilterNodeConfiguration.class; | ||
66 | + } | ||
67 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.geo; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | ||
20 | +import org.thingsboard.server.common.data.EntityType; | ||
21 | +import org.thingsboard.server.common.msg.session.SessionMsgType; | ||
22 | + | ||
23 | +import java.util.Arrays; | ||
24 | +import java.util.Collections; | ||
25 | +import java.util.List; | ||
26 | + | ||
27 | +/** | ||
28 | + * Created by ashvayka on 19.01.18. | ||
29 | + */ | ||
30 | +@Data | ||
31 | +public class TbGpsGeofencingFilterNodeConfiguration implements NodeConfiguration<TbGpsGeofencingFilterNodeConfiguration> { | ||
32 | + | ||
33 | + private String latitudeKeyName; | ||
34 | + private String longitudeKeyName; | ||
35 | + private boolean fetchPerimeterInfoFromMessageMetadata; | ||
36 | + | ||
37 | + private PerimeterType perimeterType; | ||
38 | + | ||
39 | + //For Polygons | ||
40 | + private String polygonsDefinition; | ||
41 | + | ||
42 | + //For Circles | ||
43 | + private Double centerLatitude; | ||
44 | + private Double centerLongitude; | ||
45 | + private Double range; | ||
46 | + private RangeUnit rangeUnit; | ||
47 | + | ||
48 | + @Override | ||
49 | + public TbGpsGeofencingFilterNodeConfiguration defaultConfiguration() { | ||
50 | + TbGpsGeofencingFilterNodeConfiguration configuration = new TbGpsGeofencingFilterNodeConfiguration(); | ||
51 | + configuration.setLatitudeKeyName("latitude"); | ||
52 | + configuration.setLongitudeKeyName("longitude"); | ||
53 | + configuration.setFetchPerimeterInfoFromMessageMetadata(true); | ||
54 | + return configuration; | ||
55 | + } | ||
56 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import com.google.gson.Gson; | ||
19 | +import com.google.gson.JsonElement; | ||
20 | +import com.google.gson.JsonObject; | ||
21 | +import com.google.gson.JsonParser; | ||
22 | +import com.google.gson.reflect.TypeToken; | ||
23 | +import lombok.AllArgsConstructor; | ||
24 | +import lombok.Data; | ||
25 | +import lombok.extern.slf4j.Slf4j; | ||
26 | +import org.thingsboard.rule.engine.api.TbContext; | ||
27 | +import org.thingsboard.rule.engine.api.TbNode; | ||
28 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
29 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
30 | +import org.thingsboard.rule.engine.util.EntityDetails; | ||
31 | +import org.thingsboard.server.common.data.ContactBased; | ||
32 | +import org.thingsboard.server.common.msg.TbMsg; | ||
33 | +import org.thingsboard.server.common.msg.TbMsgMetaData; | ||
34 | + | ||
35 | +import java.lang.reflect.Type; | ||
36 | +import java.util.Map; | ||
37 | +import java.util.concurrent.ExecutionException; | ||
38 | + | ||
39 | +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; | ||
40 | + | ||
41 | +@Slf4j | ||
42 | +public abstract class TbAbstractGetEntityDetailsNode<C extends TbAbstractGetEntityDetailsNodeConfiguration> implements TbNode { | ||
43 | + | ||
44 | + private static final Gson gson = new Gson(); | ||
45 | + private static final JsonParser jsonParser = new JsonParser(); | ||
46 | + private static final Type TYPE = new TypeToken<Map<String, String>>() {}.getType(); | ||
47 | + | ||
48 | + protected C config; | ||
49 | + | ||
50 | + @Override | ||
51 | + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { | ||
52 | + this.config = loadGetEntityDetailsNodeConfiguration(configuration); | ||
53 | + } | ||
54 | + | ||
55 | + @Override | ||
56 | + public void onMsg(TbContext ctx, TbMsg msg) { | ||
57 | + try { | ||
58 | + ctx.tellNext(getDetails(ctx, msg), SUCCESS); | ||
59 | + } catch (Exception e) { | ||
60 | + ctx.tellFailure(msg, e); | ||
61 | + } | ||
62 | + } | ||
63 | + | ||
64 | + @Override | ||
65 | + public void destroy() {} | ||
66 | + | ||
67 | + protected abstract C loadGetEntityDetailsNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException; | ||
68 | + | ||
69 | + protected abstract TbMsg getDetails(TbContext ctx, TbMsg msg); | ||
70 | + | ||
71 | + protected MessageData getDataAsJson(TbMsg msg) { | ||
72 | + if (this.config.isAddToMetadata()) { | ||
73 | + return new MessageData(gson.toJsonTree(msg.getMetaData().getData(), TYPE), "metadata"); | ||
74 | + } else { | ||
75 | + return new MessageData(jsonParser.parse(msg.getData()), "data"); | ||
76 | + } | ||
77 | + } | ||
78 | + | ||
79 | + protected TbMsg transformMsg(TbContext ctx, TbMsg msg, JsonElement resultObject, MessageData messageData) { | ||
80 | + if (messageData.getDataType().equals("metadata")) { | ||
81 | + Map<String, String> metadataMap = gson.fromJson(resultObject.toString(), TYPE); | ||
82 | + return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), new TbMsgMetaData(metadataMap), msg.getData()); | ||
83 | + } else { | ||
84 | + return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), gson.toJson(resultObject)); | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
88 | + protected JsonElement addContactProperties(JsonElement data, ContactBased entity, EntityDetails entityDetails, String prefix) { | ||
89 | + JsonObject dataAsObject = data.getAsJsonObject(); | ||
90 | + switch (entityDetails) { | ||
91 | + case ADDRESS: | ||
92 | + if (entity.getAddress() != null) | ||
93 | + dataAsObject.addProperty(prefix + "address", entity.getAddress()); | ||
94 | + break; | ||
95 | + case ADDRESS2: | ||
96 | + if (entity.getAddress2() != null) | ||
97 | + dataAsObject.addProperty(prefix + "address2", entity.getAddress2()); | ||
98 | + break; | ||
99 | + case CITY: | ||
100 | + if (entity.getCity() != null) dataAsObject.addProperty(prefix + "city", entity.getCity()); | ||
101 | + break; | ||
102 | + case COUNTRY: | ||
103 | + if (entity.getCountry() != null) | ||
104 | + dataAsObject.addProperty(prefix + "country", entity.getCountry()); | ||
105 | + break; | ||
106 | + case STATE: | ||
107 | + if (entity.getState() != null) dataAsObject.addProperty(prefix + "state", entity.getState()); | ||
108 | + break; | ||
109 | + case EMAIL: | ||
110 | + if (entity.getEmail() != null) dataAsObject.addProperty(prefix + "email", entity.getEmail()); | ||
111 | + break; | ||
112 | + case PHONE: | ||
113 | + if (entity.getPhone() != null) dataAsObject.addProperty(prefix + "phone", entity.getPhone()); | ||
114 | + break; | ||
115 | + case ZIP: | ||
116 | + if (entity.getZip() != null) dataAsObject.addProperty(prefix + "zip", entity.getZip()); | ||
117 | + break; | ||
118 | + case ADDITIONAL_INFO: | ||
119 | + if (entity.getAdditionalInfo().hasNonNull("description")) { | ||
120 | + dataAsObject.addProperty(prefix + "additionalInfo", entity.getAdditionalInfo().get("description").asText()); | ||
121 | + } | ||
122 | + break; | ||
123 | + } | ||
124 | + return dataAsObject; | ||
125 | + } | ||
126 | + | ||
127 | + @Data | ||
128 | + @AllArgsConstructor | ||
129 | + protected static class MessageData { | ||
130 | + private JsonElement data; | ||
131 | + private String dataType; | ||
132 | + } | ||
133 | + | ||
134 | + | ||
135 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.util.EntityDetails; | ||
20 | + | ||
21 | +import java.util.List; | ||
22 | + | ||
23 | +@Data | ||
24 | +public abstract class TbAbstractGetEntityDetailsNodeConfiguration { | ||
25 | + | ||
26 | + | ||
27 | + private List<EntityDetails> detailsList; | ||
28 | + | ||
29 | + private boolean addToMetadata; | ||
30 | + | ||
31 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import com.google.gson.JsonElement; | ||
19 | +import com.google.gson.JsonObject; | ||
20 | +import lombok.extern.slf4j.Slf4j; | ||
21 | +import org.thingsboard.rule.engine.api.RuleNode; | ||
22 | +import org.thingsboard.rule.engine.api.TbContext; | ||
23 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
24 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
25 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
26 | +import org.thingsboard.rule.engine.util.EntityDetails; | ||
27 | +import org.thingsboard.server.common.data.Customer; | ||
28 | +import org.thingsboard.server.common.data.Device; | ||
29 | +import org.thingsboard.server.common.data.EntityView; | ||
30 | +import org.thingsboard.server.common.data.asset.Asset; | ||
31 | +import org.thingsboard.server.common.data.id.AssetId; | ||
32 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
33 | +import org.thingsboard.server.common.data.id.EntityViewId; | ||
34 | +import org.thingsboard.server.common.data.plugin.ComponentType; | ||
35 | +import org.thingsboard.server.common.msg.TbMsg; | ||
36 | + | ||
37 | +@Slf4j | ||
38 | +@RuleNode(type = ComponentType.ENRICHMENT, | ||
39 | + name = "customer details", | ||
40 | + configClazz = TbGetCustomerDetailsNodeConfiguration.class, | ||
41 | + nodeDescription = "Adds fields from Customer details to the message body or metadata", | ||
42 | + nodeDetails = "If checkbox: <b>Add selected details to the message metadata</b> is selected, existing fields will be added to the message metadata instead of message data.<br><br>" + | ||
43 | + "<b>Note:</b> only Device, Asset, and Entity View type are allowed.<br><br>" + | ||
44 | + "If the originator of the message is not assigned to Customer, or originator type is not supported - Message will be forwarded to <b>Failure</b> chain, otherwise, <b>Success</b> chain will be used.", | ||
45 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | ||
46 | + configDirective = "tbEnrichmentNodeEntityDetailsConfig") | ||
47 | +public class TbGetCustomerDetailsNode extends TbAbstractGetEntityDetailsNode<TbGetCustomerDetailsNodeConfiguration> { | ||
48 | + | ||
49 | + private static final String CUSTOMER_PREFIX = "customer_"; | ||
50 | + | ||
51 | + @Override | ||
52 | + protected TbGetCustomerDetailsNodeConfiguration loadGetEntityDetailsNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { | ||
53 | + return TbNodeUtils.convert(configuration, TbGetCustomerDetailsNodeConfiguration.class); | ||
54 | + } | ||
55 | + | ||
56 | + @Override | ||
57 | + protected TbMsg getDetails(TbContext ctx, TbMsg msg) { | ||
58 | + return getCustomerTbMsg(ctx, msg, getDataAsJson(msg)); | ||
59 | + } | ||
60 | + | ||
61 | + private TbMsg getCustomerTbMsg(TbContext ctx, TbMsg msg, MessageData messageData) { | ||
62 | + JsonElement resultObject = null; | ||
63 | + if (!config.getDetailsList().isEmpty()) { | ||
64 | + for (EntityDetails entityDetails : config.getDetailsList()) { | ||
65 | + resultObject = addContactProperties(messageData.getData(), getCustomer(ctx, msg), entityDetails, CUSTOMER_PREFIX); | ||
66 | + } | ||
67 | + return transformMsg(ctx, msg, resultObject, messageData); | ||
68 | + } else { | ||
69 | + return msg; | ||
70 | + } | ||
71 | + } | ||
72 | + | ||
73 | + private Customer getCustomer(TbContext ctx, TbMsg msg) { | ||
74 | + switch (msg.getOriginator().getEntityType()) { | ||
75 | + case DEVICE: | ||
76 | + Device device = ctx.getDeviceService().findDeviceById(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId())); | ||
77 | + if (!device.getCustomerId().isNullUid()) { | ||
78 | + return ctx.getCustomerService().findCustomerById(ctx.getTenantId(), device.getCustomerId()); | ||
79 | + } else { | ||
80 | + throw new RuntimeException("Device with name '" + device.getName() + "' is not assigned to Customer."); | ||
81 | + } | ||
82 | + case ASSET: | ||
83 | + Asset asset = ctx.getAssetService().findAssetById(ctx.getTenantId(), new AssetId(msg.getOriginator().getId())); | ||
84 | + if (!asset.getCustomerId().isNullUid()) { | ||
85 | + return ctx.getCustomerService().findCustomerById(ctx.getTenantId(), asset.getCustomerId()); | ||
86 | + } else { | ||
87 | + throw new RuntimeException("Asset with name '" + asset.getName() + "' is not assigned to Customer."); | ||
88 | + } | ||
89 | + case ENTITY_VIEW: | ||
90 | + EntityView entityView = ctx.getEntityViewService().findEntityViewById(ctx.getTenantId(), new EntityViewId(msg.getOriginator().getId())); | ||
91 | + if (!entityView.getCustomerId().isNullUid()) { | ||
92 | + return ctx.getCustomerService().findCustomerById(ctx.getTenantId(), entityView.getCustomerId()); | ||
93 | + } else { | ||
94 | + throw new RuntimeException("EntityView with name '" + entityView.getName() + "' is not assigned to Customer."); | ||
95 | + } | ||
96 | + default: | ||
97 | + throw new RuntimeException("Entity with entityType '" + msg.getOriginator().getEntityType() + "' is not supported."); | ||
98 | + } | ||
99 | + } | ||
100 | + | ||
101 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | ||
20 | + | ||
21 | +import java.util.Collections; | ||
22 | + | ||
23 | +@Data | ||
24 | +public class TbGetCustomerDetailsNodeConfiguration extends TbAbstractGetEntityDetailsNodeConfiguration implements NodeConfiguration<TbGetCustomerDetailsNodeConfiguration> { | ||
25 | + | ||
26 | + | ||
27 | + @Override | ||
28 | + public TbGetCustomerDetailsNodeConfiguration defaultConfiguration() { | ||
29 | + TbGetCustomerDetailsNodeConfiguration configuration = new TbGetCustomerDetailsNodeConfiguration(); | ||
30 | + configuration.setDetailsList(Collections.emptyList()); | ||
31 | + return configuration; | ||
32 | + } | ||
33 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import com.google.gson.JsonElement; | ||
19 | +import com.google.gson.JsonObject; | ||
20 | +import lombok.extern.slf4j.Slf4j; | ||
21 | +import org.thingsboard.rule.engine.api.RuleNode; | ||
22 | +import org.thingsboard.rule.engine.api.TbContext; | ||
23 | +import org.thingsboard.rule.engine.api.TbNodeConfiguration; | ||
24 | +import org.thingsboard.rule.engine.api.TbNodeException; | ||
25 | +import org.thingsboard.rule.engine.api.util.TbNodeUtils; | ||
26 | +import org.thingsboard.rule.engine.util.EntityDetails; | ||
27 | +import org.thingsboard.server.common.data.ContactBased; | ||
28 | +import org.thingsboard.server.common.data.Tenant; | ||
29 | +import org.thingsboard.server.common.data.plugin.ComponentType; | ||
30 | +import org.thingsboard.server.common.msg.TbMsg; | ||
31 | + | ||
32 | +@Slf4j | ||
33 | +@RuleNode(type = ComponentType.ENRICHMENT, | ||
34 | + name = "tenant details", | ||
35 | + configClazz = TbGetTenantDetailsNodeConfiguration.class, | ||
36 | + nodeDescription = "Adds fields from Tenant details to the message body or metadata", | ||
37 | + nodeDetails = "If checkbox: <b>Add selected details to the message metadata</b> is selected, existing fields will be added to the message metadata instead of message data.<br><br>" + | ||
38 | + "<b>Note:</b> only Device, Asset, and Entity View type are allowed.<br><br>" + | ||
39 | + "If the originator of the message is not assigned to Tenant, or originator type is not supported - Message will be forwarded to <b>Failure</b> chain, otherwise, <b>Success</b> chain will be used.", | ||
40 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | ||
41 | + configDirective = "tbEnrichmentNodeEntityDetailsConfig") | ||
42 | +public class TbGetTenantDetailsNode extends TbAbstractGetEntityDetailsNode<TbGetTenantDetailsNodeConfiguration> { | ||
43 | + | ||
44 | + private static final String TENANT_PREFIX = "tenant_"; | ||
45 | + | ||
46 | + @Override | ||
47 | + protected TbGetTenantDetailsNodeConfiguration loadGetEntityDetailsNodeConfiguration(TbNodeConfiguration configuration) throws TbNodeException { | ||
48 | + return TbNodeUtils.convert(configuration, TbGetTenantDetailsNodeConfiguration.class); | ||
49 | + } | ||
50 | + | ||
51 | + @Override | ||
52 | + protected TbMsg getDetails(TbContext ctx, TbMsg msg) { | ||
53 | + return getTenantTbMsg(ctx, msg, getDataAsJson(msg)); | ||
54 | + } | ||
55 | + | ||
56 | + private TbMsg getTenantTbMsg(TbContext ctx, TbMsg msg, MessageData messageData) { | ||
57 | + JsonElement resultObject = null; | ||
58 | + Tenant tenant = ctx.getTenantService().findTenantById(ctx.getTenantId()); | ||
59 | + if (!config.getDetailsList().isEmpty()) { | ||
60 | + for (EntityDetails entityDetails : config.getDetailsList()) { | ||
61 | + resultObject = addContactProperties(messageData.getData(), tenant, entityDetails, TENANT_PREFIX); | ||
62 | + } | ||
63 | + return transformMsg(ctx, msg, resultObject, messageData); | ||
64 | + } else { | ||
65 | + return msg; | ||
66 | + } | ||
67 | + } | ||
68 | +} |
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.metadata; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.rule.engine.api.NodeConfiguration; | ||
20 | + | ||
21 | +import java.util.Collections; | ||
22 | + | ||
23 | +@Data | ||
24 | +public class TbGetTenantDetailsNodeConfiguration extends TbAbstractGetEntityDetailsNodeConfiguration implements NodeConfiguration<TbGetTenantDetailsNodeConfiguration> { | ||
25 | + | ||
26 | + | ||
27 | + @Override | ||
28 | + public TbGetTenantDetailsNodeConfiguration defaultConfiguration() { | ||
29 | + TbGetTenantDetailsNodeConfiguration configuration = new TbGetTenantDetailsNodeConfiguration(); | ||
30 | + configuration.setDetailsList(Collections.emptyList()); | ||
31 | + return configuration; | ||
32 | + } | ||
33 | +} |
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityDetails.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2019 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.rule.engine.util; | ||
17 | + | ||
18 | +public enum EntityDetails { | ||
19 | + | ||
20 | + COUNTRY, CITY, STATE, ZIP, ADDRESS, ADDRESS2, PHONE, EMAIL, ADDITIONAL_INFO | ||
21 | + | ||
22 | +} |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
1 | -!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(91)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.use-metadata-interval-patterns' | translate }}\" ng-model=configuration.useMessageAlarmData>{{ 'tb.rulenode.use-message-alarm-data' | translate }} </md-checkbox> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> </section> "},function(e,t){e.exports=" <section ng-form name=createRelationConfigForm layout=column style=min-width:650px> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=createRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=createRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if=\"configuration.entityType == 'DEVICE' || configuration.entityType == 'ASSET'\" style=margin-top:38px> <label translate>tb.rulenode.entity-type-pattern</label> <input ng-required=true name=entityTypePattern ng-model=configuration.entityTypePattern> <div ng-messages=createRelationConfigForm.entityTypePattern.$error> <div ng-message=required translate>tb.rulenode.entity-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex style=margin-top:0> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-checkbox flex ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" aria-label=\"{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}\" ng-model=configuration.createEntityIfNotExists>{{ 'tb.rulenode.create-entity-if-not-exists' | translate }} </md-checkbox> <div class=tb-hint ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" translate>tb.rulenode.create-entity-if-not-exists-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.remove-current-relations' | translate }}\" ng-model=configuration.removeCurrentRelations>{{ 'tb.rulenode.remove-current-relations' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.remove-current-relations-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}\" ng-model=configuration.changeOriginatorToRelatedEntity>{{ 'tb.rulenode.change-originator-to-related-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.change-originator-to-related-entity-hint</div> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=createRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=deleteRelationConfigForm layout=column> <md-checkbox aria-label=\"{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}\" ng-model=configuration.deleteForSingleEntity> {{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.delete-relation-hint</div> <md-input-container class=md-block style=min-width:100px;margin-bottom:38px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.deleteForSingleEntity> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=deleteRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=deleteRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=deleteRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}" ng-model=configuration.useSimpleClientHttpFactory> {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }} </md-checkbox> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '; | ||
2 | -},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> </section> '},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:18px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate>tb.rulenode.fetch-mode-hint</div> <md-input-container flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.order-by</label> <md-select required ng-model=configuration.orderBy> <md-option ng-repeat="type in ruleNodeTypes.samplingOrder" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.order-by-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}" ng-model=configuration.useMetadataIntervalPatterns>{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-interval-patterns-hint</div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns == false "> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns === false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true" style=margin-top:38px> <label translate>tb.rulenode.start-interval-pattern</label> <input ng-required=true name=startIntervalPattern ng-model=configuration.startIntervalPattern> <div ng-messages=getTelemetryConfigForm.startIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.start-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.start-interval-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true"> <label translate>tb.rulenode.end-interval-pattern</label> <input ng-required=true name=endIntervalPattern ng-model=configuration.endIntervalPattern> <div ng-messages=getTelemetryConfigForm.endIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.end-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.end-interval-pattern-hint</div> </md-input-container> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},28,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding tb-required">tb.rulenode.data-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.metadataNames).length readonly=readonly ng-model=configuration.messageNames placeholder="{{\'tb.rulenode.data-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <label translate class="tb-title no-padding tb-required">tb.rulenode.metadata-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.messageNames).length readonly=readonly ng-model=configuration.metadataNames placeholder="{{\'tb.rulenode.metadata-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.check-all-keys\' | translate }}" ng-model=configuration.checkAllKeys>{{ \'tb.rulenode.check-all-keys\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-all-keys-hint</div> </section> '},function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-checkbox aria-label=\"{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}\" ng-model=configuration.checkForSingleEntity> {{ 'tb.rulenode.check-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-relation-hint</div> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.checkForSingleEntity style=padding-top:20px> <tb-entity-type-select style=min-width:100px;padding-bottom:20px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "; | ||
3 | -},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var d=o.default;i.html(d),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var d=o.default;i.html(d),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var d=o.default;i.html(d),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(66),i=a(r),o=n(47),l=a(o),s=n(52),d=a(s),u=n(49),c=a(u),m=n(48),g=a(m),p=n(55),f=a(p),b=n(61),v=a(b),y=n(62),h=a(y),q=n(60),$=a(q),k=n(54),x=a(k),T=n(64),C=a(T),w=n(65),M=a(w),S=n(59),_=a(S),N=n(56),E=a(N),V=n(63),P=a(V),F=n(58),j=a(F),A=n(57),O=a(A),I=n(46),R=a(I),K=n(67),D=a(K),U=n(51),L=a(U),z=n(50),B=a(z);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",x.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",j.default).directive("tbActionNodeMsgCountConfig",O.default).directive("tbActionNodeAssignToCustomerConfig",R.default).directive("tbActionNodeUnAssignToCustomerConfig",D.default).directive("tbActionNodeDeleteRelationConfig",L.default).directive("tbActionNodeCreateRelationConfig",B.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s);var d=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(74),i=a(r),o=n(75),l=a(o),s=n(71),d=a(s),u=n(76),c=a(u),m=n(70),g=a(m),p=n(77),f=a(p),b=n(72),v=a(b);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(31),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(32),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(34),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(35),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(36),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(83),i=a(r),o=n(81),l=a(o),s=n(84),d=a(s),u=n(79),c=a(u),m=n(82),g=a(m),p=n(78),f=a(p);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,d()}}function d(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var u=o.default;r.html(u),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.messageTypesWatch=a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(4);var i=n(37),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(38),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(39),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(40),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(41),o=a(i);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(42),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(43),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(87),i=a(r),o=n(89),l=a(o),s=n(90),d=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(44),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(45),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(94),i=a(r),o=n(80),l=a(o),s=n(73),d=a(s),u=n(88),c=a(u),m=n(53),g=a(m),p=n(69),f=a(p),b=n(86),v=a(b),y=n(68),h=a(y),q=n(85),$=a(q),k=n(93),x=a(k);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(x.default).name},function(e,t){"use strict";function n(e){var t={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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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.", | ||
4 | -"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.","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","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-hint":"Relation type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","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","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.","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",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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> 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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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 <code>${metaKeyName}</code> to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use <code>${metaKeyName}</code> 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","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.'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){(0,o.default)(e)}r.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(92),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); | 1 | +!function(e){function t(i){if(n[i])return n[i].exports;var a=n[i]={exports:{},id:i,loaded:!1};return e[i].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),i=e[t[0]];return function(e,t,a){i.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(99)},function(e,t){},1,1,1,1,function(e,t){e.exports=" <section ng-form name=assignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=assignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-checkbox aria-label=\"{{ 'tb.rulenode.create-group-if-not-exists' | translate }}\" ng-model=configuration.createCustomerIfNotExists>{{ 'tb.rulenode.create-customer-if-not-exists' | translate }} </md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=assignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-checkbox aria-label=\"{{ 'tb.rulenode.use-metadata-interval-patterns' | translate }}\" ng-model=configuration.useMessageAlarmData>{{ 'tb.rulenode.use-message-alarm-data' | translate }} </md-checkbox> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <section layout=column layout-gt-sm=row ng-if=!configuration.useMessageAlarmData> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> </section> "},function(e,t){e.exports=" <section ng-form name=createRelationConfigForm layout=column style=min-width:650px> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=createRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=createRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if=\"configuration.entityType == 'DEVICE' || configuration.entityType == 'ASSET'\" style=margin-top:38px> <label translate>tb.rulenode.entity-type-pattern</label> <input ng-required=true name=entityTypePattern ng-model=configuration.entityTypePattern> <div ng-messages=createRelationConfigForm.entityTypePattern.$error> <div ng-message=required translate>tb.rulenode.entity-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-type-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex style=margin-top:0> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-checkbox flex ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" aria-label=\"{{ 'tb.rulenode.create-entity-if-not-exists' | translate }}\" ng-model=configuration.createEntityIfNotExists>{{ 'tb.rulenode.create-entity-if-not-exists' | translate }} </md-checkbox> <div class=tb-hint ng-if=\"configuration.entityType == 'CUSTOMER' || configuration.entityType == 'ASSET' || configuration.entityType == 'DEVICE'\" translate>tb.rulenode.create-entity-if-not-exists-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.remove-current-relations' | translate }}\" ng-model=configuration.removeCurrentRelations>{{ 'tb.rulenode.remove-current-relations' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.remove-current-relations-hint</div> <md-checkbox flex aria-label=\"{{ 'tb.rulenode.change-originator-to-related-entity' | translate }}\" ng-model=configuration.changeOriginatorToRelatedEntity>{{ 'tb.rulenode.change-originator-to-related-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.change-originator-to-related-entity-hint</div> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=createRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=deleteRelationConfigForm layout=column> <md-checkbox aria-label=\"{{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }}\" ng-model=configuration.deleteForSingleEntity> {{ 'tb.rulenode.delete-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.delete-relation-hint</div> <md-input-container class=md-block style=min-width:100px;margin-bottom:38px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.deleteForSingleEntity> <md-input-container class=md-block> <tb-entity-type-select style=min-width:100px the-form=deleteRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> </md-input-container> <md-input-container class=md-block flex ng-if=configuration.entityType style=margin-top:38px> <label translate>tb.rulenode.entity-name-pattern</label> <input ng-required=true name=entityNamePattern ng-model=configuration.entityNamePattern> <div ng-messages=deleteRelationConfigForm.entityNamePattern.$error> <div ng-message=required translate>tb.rulenode.entity-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.entity-name-pattern-hint</div> </md-input-container> </div> <md-input-container class=md-block flex> <label translate>tb.rulenode.relation-type-pattern</label> <input ng-required=true name=relationType ng-model=configuration.relationType> <div ng-messages=createRelationConfigForm.relationType.$error> <div ng-message=required translate>tb.rulenode.relation-type-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.relation-type-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.entity-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=entityCacheExpiration ng-model=configuration.entityCacheExpiration> <div ng-messages=deleteRelationConfigForm.entityCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.entity-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.entity-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.entity-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=geoActionConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoActionConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoActionConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=0 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoActionConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=0 step=0.1 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoActionConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoActionConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType===ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoActionConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minInsideDuration ng-model=configuration.minInsideDuration> <div ng-messages=geoActionConfigForm.minInsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-inside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-inside-duration-time-unit</label> <md-select required name=minInsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-inside-duration-time-unit\' | translate }}" ng-model=configuration.minInsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration</label> <input required type=number step=1 min=1 max=2147483647 name=minOutsideDuration ng-model=configuration.minOutsideDuration> <div ng-messages=geoActionConfigForm.minOutsideDuration.$error> <div translate ng-message=required>tb.rulenode.min-outside-duration-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex> <label translate class="tb-title no-padding">tb.rulenode.min-outside-duration-time-unit</label> <md-select required name=minOutsideDurationTimeUnit aria-label="{{ \'tb.rulenode.min-outside-duration-time-unit\' | translate }}" ng-model=configuration.minOutsideDurationTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </section> '},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgCountConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.interval-seconds</label> <input ng-required=true type=number step=1 name=interval ng-model=configuration.interval min=1> <div ng-messages=msgCountConfigForm.interval.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.interval-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-interval-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.output-timeseries-key-prefix</label> <input ng-required=true name=telemetryPrefix ng-model=configuration.telemetryPrefix> <div ng-messages=msgCountConfigForm.telemetryPrefix.$error> <div translate ng-message=required>tb.rulenode.output-timeseries-key-prefix-required</div> </div> </md-input-container> </section> "; |
2 | +},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}" ng-model=configuration.useSimpleClientHttpFactory> {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }} </md-checkbox> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.custom-table-name</label> <input ng-required=true name=tableName ng-model=configuration.tableName> <div ng-messages=saveToCustomTableConfigForm.tableName.$error> <div ng-message=required translate>tb.rulenode.custom-table-name-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.custom-table-hint</div> </md-input-container> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.message-field\'" key-required-text="\'tb.rulenode.message-field-required\'" val-text="\'tb.rulenode.table-col\'" val-required-text="\'tb.rulenode.table-col-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=unAssignCustomerConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.customer-name-pattern</label> <input ng-required=true name=customerNamePattern ng-model=configuration.customerNamePattern> <div ng-messages=unAssignCustomerConfigForm.customerNamePattern.$error> <div ng-message=required translate>tb.rulenode.customer-name-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.customer-name-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.customer-cache-expiration</label> <input type=number step=1 min=0 ng-required=true name=customerCacheExpiration ng-model=configuration.customerCacheExpiration> <div ng-messages=unAssignCustomerConfigForm.customerCacheExpiration.$error> <div translate ng-message=required>tb.rulenode.customer-cache-expiration-required</div> <div translate ng-message=min>tb.rulenode.customer-cache-expiration-range</div> </div> <div class=tb-hint translate>tb.rulenode.customer-cache-expiration-hint</div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.entity-details</label> <md-chips readonly=disabled style=margin-bottom:28px id=entityDetailsListChips ng-required=tbRequired ng-model=configuration.detailsList placeholder={{placeholder}} secondary-placeholder={{secondaryPlaceholder}} md-autocomplete-snap md-require-match=true> <md-autocomplete md-no-cache=true id=entityDetails md-selected-item=selectedEntityDetail md-selected-item-change=selectedItemChange(item) md-search-text=entityDetailsSearchText md-items="item in entityDetailsList" md-item-text=item md-min-length=0 placeholder="{{ (!ruleNodeTypes.entityDetails || !ruleNodeTypes.entityDetails.length) ? placeholder : secondaryPlaceholder }}"> <md-item-template> <span md-highlight-text=entityDetailsSearchText md-highlight-flags=^i> {{\'tb.rulenode.entity-details-\'+item.toLowerCase() | translate}} </span> </md-item-template> <md-not-found> <span translate translate-values="{ entityDetails: entityDetailsSearchText }">tb.rulenode.no-entity-details-matching</span> </md-not-found> </md-autocomplete> <md-chip-template> <span> <strong>{{\'tb.rulenode.entity-details-\'+$chip.toLowerCase() | translate}}</strong> </span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error ng-if="inputTouched && tbRequired" role=alert> <div translate ng-message=configuration.detailsList class=tb-error-message>tb.rulenode.entity-details-list-empty</div> </div> <md-checkbox aria-label="{{ \'tb.rulenode.add-to-metadata\' | translate }}" ng-model=configuration.addToMetadata> {{ \'tb.rulenode.add-to-metadata\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.add-to-metadata-hint</div> </section> '},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:18px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate>tb.rulenode.fetch-mode-hint</div> <md-input-container flex ng-if="configuration.fetchMode === \'ALL\' "> <label translate class="tb-title no-padding">tb.rulenode.order-by</label> <md-select required ng-model=configuration.orderBy> <md-option ng-repeat="type in ruleNodeTypes.samplingOrder" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div class=tb-hint translate flex ng-if="configuration.fetchMode === \'ALL\' ">tb.rulenode.order-by-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}" ng-model=configuration.useMetadataIntervalPatterns>{{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.use-metadata-interval-patterns-hint</div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns == false "> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value" flex ng-if="configuration.useMetadataIntervalPatterns == false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit" flex ng-if="configuration.useMetadataIntervalPatterns === false"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true" style=margin-top:38px> <label translate>tb.rulenode.start-interval-pattern</label> <input ng-required=true name=startIntervalPattern ng-model=configuration.startIntervalPattern> <div ng-messages=getTelemetryConfigForm.startIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.start-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.start-interval-pattern-hint</div> </md-input-container> <md-input-container class=md-block flex ng-if="configuration.useMetadataIntervalPatterns === true"> <label translate>tb.rulenode.end-interval-pattern</label> <input ng-required=true name=endIntervalPattern ng-model=configuration.endIntervalPattern> <div ng-messages=getTelemetryConfigForm.endIntervalPattern.$error> <div ng-message=required translate>tb.rulenode.end-interval-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.end-interval-pattern-hint</div> </md-input-container> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},30,function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding tb-required">tb.rulenode.data-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.metadataNames).length readonly=readonly ng-model=configuration.messageNames placeholder="{{\'tb.rulenode.data-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <label translate class="tb-title no-padding tb-required">tb.rulenode.metadata-keys</label> <md-chips style=padding-bottom:15px ng-required=!(configuration.messageNames).length readonly=readonly ng-model=configuration.metadataNames placeholder="{{\'tb.rulenode.metadata-keys\' | translate}}" md-separator-keys=separatorKeys md-add-on-blur=true> </md-chips> <div class=tb-hint translate>tb.rulenode.separator-hint</div> <md-checkbox aria-label="{{ \'tb.rulenode.check-all-keys\' | translate }}" ng-model=configuration.checkAllKeys>{{ \'tb.rulenode.check-all-keys\' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-all-keys-hint</div> </section> '},function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-checkbox aria-label=\"{{ 'tb.rulenode.check-relation-to-specific-entity' | translate }}\" ng-model=configuration.checkForSingleEntity> {{ 'tb.rulenode.check-relation-to-specific-entity' | translate }} </md-checkbox> <div class=tb-hint translate>tb.rulenode.check-relation-hint</div> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select ng-if=configuration.checkForSingleEntity style=padding-top:20px> <tb-entity-type-select style=min-width:100px;padding-bottom:20px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "; | ||
3 | +},function(e,t){e.exports=' <section ng-form name=geoFilterConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.latitude-key-name</label> <input ng-required=true name=latitudeKeyName ng-model=configuration.latitudeKeyName> <div ng-messages=geoFilterConfigForm.latitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.latitude-key-name-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.longitude-key-name</label> <input ng-required=true name=longitudeKeyName ng-model=configuration.longitudeKeyName> <div ng-messages=geoFilterConfigForm.longitudeKeyName.$error> <div ng-message=required translate>tb.rulenode.longitude-key-name-required</div> </div> </md-input-container> <md-checkbox flex aria-label="{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}" ng-model=configuration.fetchPerimeterInfoFromMessageMetadata>{{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }} </md-checkbox> <div layout=row class=tb-entity-select ng-if="configuration.fetchPerimeterInfoFromMessageMetadata === false"> <md-input-container class=md-block flex=100> <label translate>tb.rulenode.perimeter-type</label> <md-select required ng-model=configuration.perimeterType flex> <md-option ng-repeat="type in ruleNodeTypes.perimeterType" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.CIRCLE.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=50> <md-input-container class=md-block flex layout=column style=margin-top:44px> <label translate>tb.rulenode.circle-center-latitude</label> <input type=number min=0 step=0.1 ng-required=true name=centerLatitude ng-model=configuration.centerLatitude> <div ng-messages=geoFilterConfigForm.centerLatitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-latitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block flex style=margin-top:44px> <label translate>tb.rulenode.circle-center-longitude</label> <input type=number min=0 step=0.1 ng-required=true name=centerLongitude ng-model=configuration.centerLongitude> <div ng-messages=geoFilterConfigForm.centerLongitude.$error> <div ng-message=required translate>tb.rulenode.circle-center-longitude-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range</label> <input type=number min=0 step=0.1 ng-required=true name=range ng-model=configuration.range> <div ng-messages=geoFilterConfigForm.range.$error> <div ng-message=required translate>tb.rulenode.range-required</div> </div> </md-input-container> </div> <div layout=column flex=50> <md-input-container class=md-block style=margin-top:28px> <label translate>tb.rulenode.range-units</label> <md-select required ng-model=configuration.rangeUnit> <md-option ng-repeat="type in ruleNodeTypes.rangeUnit" ng-value=type.value> {{ type.name | translate}} </md-option> </md-select> </md-input-container> </div> </div> <div layout=row layout-wrap ng-if="configuration.perimeterType === ruleNodeTypes.perimeterType.POLYGON.value && configuration.fetchPerimeterInfoFromMessageMetadata === false"> <div layout=column flex=100> <md-input-container class=md-block style=margin-top:44px> <label translate>tb.rulenode.polygon-definition</label> <input ng-required=true name=polygonsDefinition ng-model=configuration.polygonsDefinition> <div ng-messages=geoFilterConfigForm.polygonsDefinition.$error> <div ng-message=required translate>tb.rulenode.polygon-definition-required</div> </div> <div class=tb-hint style=margin-top:5px translate>tb.rulenode.polygon-definition-hint</div> </md-input-container> </div> </div> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(6),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(7),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(8),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);i.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(9),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(10),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(11),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,i){var a=function(a,r,l,s){var d=o.default;r.html(d),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);i.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var r=n(12),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(13),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(72),r=i(a),o=n(51),l=i(o),s=n(56),d=i(s),u=n(53),c=i(u),m=n(52),g=i(m),p=n(60),f=i(p),b=n(66),v=i(b),y=n(67),h=i(y),q=n(65),$=i(q),x=n(59),k=i(x),T=n(70),C=i(T),w=n(71),M=i(w),N=n(64),_=i(N),S=n(61),E=i(S),P=n(69),F=i(P),V=n(63),A=i(V),I=n(62),j=i(I),O=n(50),D=i(O),R=n(73),K=i(R),L=n(55),U=i(L),z=n(54),B=i(z),H=n(68),Y=i(H),G=n(57),Q=i(G);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",r.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",d.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",h.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",E.default).directive("tbActionNodeSendEmailConfig",F.default).directive("tbActionNodeMsgDelayConfig",A.default).directive("tbActionNodeMsgCountConfig",j.default).directive("tbActionNodeAssignToCustomerConfig",D.default).directive("tbActionNodeUnAssignToCustomerConfig",K.default).directive("tbActionNodeDeleteRelationConfig",U.default).directive("tbActionNodeCreateRelationConfig",B.default).directive("tbActionNodeCustomTableConfig",Y.default).directive("tbActionNodeGpsGeofencingConfig",Q.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(14),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(15),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$mdExpansionPanel=t,i.ruleNodeTypes=n,i.credentialsTypeChanged=function(){var e=i.configuration.credentials.type;i.configuration.credentials={},i.configuration.credentials.type=e,i.updateValidity()},i.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){i.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(i.configuration.credentials.caCertFileName=e.name,i.configuration.credentials.caCert=a),"privateKey"==t&&(i.configuration.credentials.privateKeyFileName=e.name,i.configuration.credentials.privateKey=a),"Cert"==t&&(i.configuration.credentials.certFileName=e.name,i.configuration.credentials.cert=a)),i.updateValidity()}})},n.readAsText(e.file)},i.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(i.configuration.credentials.caCertFileName=null,i.configuration.credentials.caCert=null),"privateKey"==e&&(i.configuration.credentials.privateKeyFileName=null,i.configuration.credentials.privateKey=null),"Cert"==e&&(i.configuration.credentials.certFileName=null,i.configuration.credentials.cert=null),i.updateValidity()},i.updateValidity=function(){var e=!0,t=i.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var r=n(16),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(17),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(18),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(19),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(20),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(21),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(22),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(23),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(24),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(25),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(26),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(27),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(28),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(29),o=i(r)},function(e,t){"use strict";function n(e){var t=function(t,n,i,a){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(30),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(31),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),n.entityDetailsList=[];for(var s in t.entityDetails){var d=s;n.entityDetailsList.push(d)}r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(32),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){ | ||
4 | +var s=o.default;a.html(s);var d=186;i.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,d],i.ruleNodeTypes=n,i.aggPeriodTimeUnits={},i.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,i.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,i.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,i.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,i.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{},link:i}}a.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(33),o=i(r);n(3)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(81),r=i(a),o=n(82),l=i(o),s=n(77),d=i(s),u=n(83),c=i(u),m=n(76),g=i(m),p=n(84),f=i(p),b=n(79),v=i(b),y=n(78),h=i(y);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",r.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",d.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).directive("tbEnrichmentNodeEntityDetailsConfig",h.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(34),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(35),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(36),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(37),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(38),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(39),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(40),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(91),r=i(a),o=n(89),l=i(o),s=n(92),d=i(s),u=n(86),c=i(u),m=n(90),g=i(m),p=n(85),f=i(p),b=n(87),v=i(b);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",r.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",d.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).directive("tbFilterNodeCheckMessageConfig",f.default).directive("tbFilterNodeGpsGeofencingConfig",v.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<i.messageTypes.length;t++)e.push(i.messageTypes[t].value);l.$viewValue.messageTypes=e,d()}}function d(){if(i.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var u=o.default;a.html(u),i.selectedMessageType=null,i.messageTypeSearchText=null,i.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}i.transformMessageTypeChip=function(e){var n,i=t("filter")(c,{name:e},!0);return n=i&&i.length?angular.copy(i[0]):{name:e,value:e}},i.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},i.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,i=angular.element(n),r=i.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),i.scope().$mdChipsCtrl.appendChip(r.trim()),i.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){i.messageTypesWatch&&(i.messageTypesWatch(),i.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var r=e.messageTypes[a];n.messageType[r]?t.push(angular.copy(n.messageType[r])):t.push({name:r,value:r})}i.messageTypes=t,i.messageTypesWatch=i.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:i}}a.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(4);var r=n(41),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(42),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(43),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(44),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){function r(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),d()}function d(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var u=o.default;n.html(u),t.ngModelCtrl=a,t.removeKeyVal=r,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),d()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(45),o=i(r);n(5)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||r.$setViewValue(n.query)}),r.$render=function(){n.query=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(46),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,i,a,r){var l=o.default;i.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||r.$setViewValue(n.configuration)}),r.$render=function(){n.configuration=r.$viewValue},e(i.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(47),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(95),r=i(a),o=n(97),l=i(o),s=n(98),d=i(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",r.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",d.default).name},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var i=function(i,a,r,l){var s=o.default;a.html(s),i.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(i.configuration)}),l.$render=function(){i.configuration=l.$viewValue},i.testScript=function(e){var a=angular.copy(i.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i.ruleNodeId).then(function(e){i.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(i)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:i}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(48),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,i,a){var r=o.default;n.html(r),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(49),o=i(r)},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(102),r=i(a),o=n(88),l=i(o),s=n(80),d=i(s),u=n(96),c=i(u),m=n(58),g=i(m),p=n(75),f=i(p),b=n(94),v=i(b),y=n(74),h=i(y),q=n(93),$=i(q),x=n(101),k=i(x);t.default=angular.module("thingsboard.ruleChain.config",[r.default,l.default,d.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",h.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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.","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","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-hint":"Relation type pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","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","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.","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",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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> 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","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","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"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 <code>${metaKeyName}</code> to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use <code>${metaKeyName}</code> 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","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-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],[lon2,lon4], ... ,[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"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var r=n(100),o=i(r)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],samplingOrder:["ASC","DESC"],httpRequestType:["GET","POST","PUT","DELETE"],entityDetails:{COUNTRY:{name:"tb.rulenode.entity-details-country",value:"COUNTRY"},STATE:{name:"tb.rulenode.entity-details-state",value:"STATE"},ZIP:{name:"tb.rulenode.entity-details-zip",value:"ZIP"},ADDRESS:{name:"tb.rulenode.entity-details-address",value:"ADDRESS"},ADDRESS2:{name:"tb.rulenode.entity-details-address2",value:"ADDRESS2"},PHONE:{name:"tb.rulenode.entity-details-phone",value:"PHONE"},EMAIL:{name:"tb.rulenode.entity-details-email",value:"EMAIL"},ADDITIONAL_INFO:{name:"tb.rulenode.entity-details-additional_info", | ||
5 | +value:"ADDITIONAL_INFO"}},sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},perimeterType:{CIRCLE:{name:"tb.rulenode.perimeter-circle",value:"CIRCLE"},POLYGON:{name:"tb.rulenode.perimeter-polygon",value:"POLYGON"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},rangeUnit:{METER:{value:"METER",name:"tb.rulenode.range-unit-meter"},KILOMETER:{value:"KILOMETER",name:"tb.rulenode.range-unit-kilometer"},FOOT:{value:"FOOT",name:"tb.rulenode.range-unit-foot"},MILE:{value:"MILE",name:"tb.rulenode.range-unit-mile"},NAUTICAL_MILE:{value:"NAUTICAL_MILE",name:"tb.rulenode.range-unit-nautical-mile"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); | ||
5 | //# sourceMappingURL=rulenode-core-config.js.map | 6 | //# sourceMappingURL=rulenode-core-config.js.map |
@@ -468,9 +468,9 @@ | @@ -468,9 +468,9 @@ | ||
468 | "integrity": "sha512-o+V/OzwNGpS30QmgP7DJWTdBJ2BMDut481qqB72sM0L59dkO6TNjRV7qubQCntGqGe98h9vObweQUVYTfEO4vg==" | 468 | "integrity": "sha512-o+V/OzwNGpS30QmgP7DJWTdBJ2BMDut481qqB72sM0L59dkO6TNjRV7qubQCntGqGe98h9vObweQUVYTfEO4vg==" |
469 | }, | 469 | }, |
470 | "angular-material": { | 470 | "angular-material": { |
471 | - "version": "1.1.9", | ||
472 | - "resolved": "https://registry.npmjs.org/angular-material/-/angular-material-1.1.9.tgz", | ||
473 | - "integrity": "sha512-kxyigi+7823k/31qQ0j6wL5FkCe/mw2bAg1kfEFzIvhUoe5Myr+0YoQyN8D8EGaaOyolXU/VPtxgKSfOCSLEBw==" | 471 | + "version": "1.1.13", |
472 | + "resolved": "https://registry.npmjs.org/angular-material/-/angular-material-1.1.13.tgz", | ||
473 | + "integrity": "sha512-qWc5WOhRa/sbQmiRwenOla2Pky3w+wgW0l5Wp3J6jmB/WWxMWW7+JMdCXo1diGEETTKTF2vLdeWTceDTNehmSw==" | ||
474 | }, | 474 | }, |
475 | "angular-material-data-table": { | 475 | "angular-material-data-table": { |
476 | "version": "0.10.10", | 476 | "version": "0.10.10", |
@@ -5239,12 +5239,14 @@ | @@ -5239,12 +5239,14 @@ | ||
5239 | "balanced-match": { | 5239 | "balanced-match": { |
5240 | "version": "1.0.0", | 5240 | "version": "1.0.0", |
5241 | "bundled": true, | 5241 | "bundled": true, |
5242 | - "dev": true | 5242 | + "dev": true, |
5243 | + "optional": true | ||
5243 | }, | 5244 | }, |
5244 | "brace-expansion": { | 5245 | "brace-expansion": { |
5245 | "version": "1.1.11", | 5246 | "version": "1.1.11", |
5246 | "bundled": true, | 5247 | "bundled": true, |
5247 | "dev": true, | 5248 | "dev": true, |
5249 | + "optional": true, | ||
5248 | "requires": { | 5250 | "requires": { |
5249 | "balanced-match": "^1.0.0", | 5251 | "balanced-match": "^1.0.0", |
5250 | "concat-map": "0.0.1" | 5252 | "concat-map": "0.0.1" |
@@ -5259,17 +5261,20 @@ | @@ -5259,17 +5261,20 @@ | ||
5259 | "code-point-at": { | 5261 | "code-point-at": { |
5260 | "version": "1.1.0", | 5262 | "version": "1.1.0", |
5261 | "bundled": true, | 5263 | "bundled": true, |
5262 | - "dev": true | 5264 | + "dev": true, |
5265 | + "optional": true | ||
5263 | }, | 5266 | }, |
5264 | "concat-map": { | 5267 | "concat-map": { |
5265 | "version": "0.0.1", | 5268 | "version": "0.0.1", |
5266 | "bundled": true, | 5269 | "bundled": true, |
5267 | - "dev": true | 5270 | + "dev": true, |
5271 | + "optional": true | ||
5268 | }, | 5272 | }, |
5269 | "console-control-strings": { | 5273 | "console-control-strings": { |
5270 | "version": "1.1.0", | 5274 | "version": "1.1.0", |
5271 | "bundled": true, | 5275 | "bundled": true, |
5272 | - "dev": true | 5276 | + "dev": true, |
5277 | + "optional": true | ||
5273 | }, | 5278 | }, |
5274 | "core-util-is": { | 5279 | "core-util-is": { |
5275 | "version": "1.0.2", | 5280 | "version": "1.0.2", |
@@ -5386,7 +5391,8 @@ | @@ -5386,7 +5391,8 @@ | ||
5386 | "inherits": { | 5391 | "inherits": { |
5387 | "version": "2.0.3", | 5392 | "version": "2.0.3", |
5388 | "bundled": true, | 5393 | "bundled": true, |
5389 | - "dev": true | 5394 | + "dev": true, |
5395 | + "optional": true | ||
5390 | }, | 5396 | }, |
5391 | "ini": { | 5397 | "ini": { |
5392 | "version": "1.3.5", | 5398 | "version": "1.3.5", |
@@ -5398,6 +5404,7 @@ | @@ -5398,6 +5404,7 @@ | ||
5398 | "version": "1.0.0", | 5404 | "version": "1.0.0", |
5399 | "bundled": true, | 5405 | "bundled": true, |
5400 | "dev": true, | 5406 | "dev": true, |
5407 | + "optional": true, | ||
5401 | "requires": { | 5408 | "requires": { |
5402 | "number-is-nan": "^1.0.0" | 5409 | "number-is-nan": "^1.0.0" |
5403 | } | 5410 | } |
@@ -5412,6 +5419,7 @@ | @@ -5412,6 +5419,7 @@ | ||
5412 | "version": "3.0.4", | 5419 | "version": "3.0.4", |
5413 | "bundled": true, | 5420 | "bundled": true, |
5414 | "dev": true, | 5421 | "dev": true, |
5422 | + "optional": true, | ||
5415 | "requires": { | 5423 | "requires": { |
5416 | "brace-expansion": "^1.1.7" | 5424 | "brace-expansion": "^1.1.7" |
5417 | } | 5425 | } |
@@ -5419,12 +5427,14 @@ | @@ -5419,12 +5427,14 @@ | ||
5419 | "minimist": { | 5427 | "minimist": { |
5420 | "version": "0.0.8", | 5428 | "version": "0.0.8", |
5421 | "bundled": true, | 5429 | "bundled": true, |
5422 | - "dev": true | 5430 | + "dev": true, |
5431 | + "optional": true | ||
5423 | }, | 5432 | }, |
5424 | "minipass": { | 5433 | "minipass": { |
5425 | "version": "2.2.4", | 5434 | "version": "2.2.4", |
5426 | "bundled": true, | 5435 | "bundled": true, |
5427 | "dev": true, | 5436 | "dev": true, |
5437 | + "optional": true, | ||
5428 | "requires": { | 5438 | "requires": { |
5429 | "safe-buffer": "^5.1.1", | 5439 | "safe-buffer": "^5.1.1", |
5430 | "yallist": "^3.0.0" | 5440 | "yallist": "^3.0.0" |
@@ -5443,6 +5453,7 @@ | @@ -5443,6 +5453,7 @@ | ||
5443 | "version": "0.5.1", | 5453 | "version": "0.5.1", |
5444 | "bundled": true, | 5454 | "bundled": true, |
5445 | "dev": true, | 5455 | "dev": true, |
5456 | + "optional": true, | ||
5446 | "requires": { | 5457 | "requires": { |
5447 | "minimist": "0.0.8" | 5458 | "minimist": "0.0.8" |
5448 | } | 5459 | } |
@@ -5523,7 +5534,8 @@ | @@ -5523,7 +5534,8 @@ | ||
5523 | "number-is-nan": { | 5534 | "number-is-nan": { |
5524 | "version": "1.0.1", | 5535 | "version": "1.0.1", |
5525 | "bundled": true, | 5536 | "bundled": true, |
5526 | - "dev": true | 5537 | + "dev": true, |
5538 | + "optional": true | ||
5527 | }, | 5539 | }, |
5528 | "object-assign": { | 5540 | "object-assign": { |
5529 | "version": "4.1.1", | 5541 | "version": "4.1.1", |
@@ -5535,6 +5547,7 @@ | @@ -5535,6 +5547,7 @@ | ||
5535 | "version": "1.4.0", | 5547 | "version": "1.4.0", |
5536 | "bundled": true, | 5548 | "bundled": true, |
5537 | "dev": true, | 5549 | "dev": true, |
5550 | + "optional": true, | ||
5538 | "requires": { | 5551 | "requires": { |
5539 | "wrappy": "1" | 5552 | "wrappy": "1" |
5540 | } | 5553 | } |
@@ -5656,6 +5669,7 @@ | @@ -5656,6 +5669,7 @@ | ||
5656 | "version": "1.0.2", | 5669 | "version": "1.0.2", |
5657 | "bundled": true, | 5670 | "bundled": true, |
5658 | "dev": true, | 5671 | "dev": true, |
5672 | + "optional": true, | ||
5659 | "requires": { | 5673 | "requires": { |
5660 | "code-point-at": "^1.0.0", | 5674 | "code-point-at": "^1.0.0", |
5661 | "is-fullwidth-code-point": "^1.0.0", | 5675 | "is-fullwidth-code-point": "^1.0.0", |
@@ -7675,6 +7689,22 @@ | @@ -7675,6 +7689,22 @@ | ||
7675 | } | 7689 | } |
7676 | } | 7690 | } |
7677 | }, | 7691 | }, |
7692 | + "jstree": { | ||
7693 | + "version": "3.3.7", | ||
7694 | + "resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.7.tgz", | ||
7695 | + "integrity": "sha512-yzzalO1TbZ4HdPezO43LesGI4Wv2sB0Nl+4GfwO0YYvehGws5qtTAhlBISxfur9phMLwCtf9GjHlRx2ZLXyRnw==", | ||
7696 | + "requires": { | ||
7697 | + "jquery": ">=1.9.1" | ||
7698 | + } | ||
7699 | + }, | ||
7700 | + "jstree-bootstrap-theme": { | ||
7701 | + "version": "1.0.1", | ||
7702 | + "resolved": "https://registry.npmjs.org/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz", | ||
7703 | + "integrity": "sha1-fV7cc6hG6Np/lPV6HMXd7p2eq0s=", | ||
7704 | + "requires": { | ||
7705 | + "jquery": ">=1.9.1" | ||
7706 | + } | ||
7707 | + }, | ||
7678 | "keycode": { | 7708 | "keycode": { |
7679 | "version": "2.2.0", | 7709 | "version": "2.2.0", |
7680 | "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", | 7710 | "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", |
@@ -8328,6 +8358,18 @@ | @@ -8328,6 +8358,18 @@ | ||
8328 | "tinycolor2": "*" | 8358 | "tinycolor2": "*" |
8329 | } | 8359 | } |
8330 | }, | 8360 | }, |
8361 | + "md-date-range-picker": { | ||
8362 | + "version": "0.8.4", | ||
8363 | + "resolved": "https://registry.npmjs.org/md-date-range-picker/-/md-date-range-picker-0.8.4.tgz", | ||
8364 | + "integrity": "sha512-TgLyozMJypi92yvXaljLcermTFhd1+0rlaVwV+Duo0EplbKfDJfFF3WohWhB7VmPwJNP//o44sUlecY+r/ZvXA==", | ||
8365 | + "requires": { | ||
8366 | + "angular": "^1.5.8", | ||
8367 | + "angular-animate": "^1.5.8", | ||
8368 | + "angular-aria": "^1.5.8", | ||
8369 | + "angular-material": "^1.1.0", | ||
8370 | + "angular-messages": "^1.5.8" | ||
8371 | + } | ||
8372 | + }, | ||
8331 | "mdPickers": { | 8373 | "mdPickers": { |
8332 | "version": "git://github.com/alenaksu/mdPickers.git#72592ae51c81a7260701055ea21870efa57fa7c8", | 8374 | "version": "git://github.com/alenaksu/mdPickers.git#72592ae51c81a7260701055ea21870efa57fa7c8", |
8333 | "from": "git://github.com/alenaksu/mdPickers.git#0.7.5" | 8375 | "from": "git://github.com/alenaksu/mdPickers.git#0.7.5" |
@@ -27,7 +27,7 @@ | @@ -27,7 +27,7 @@ | ||
27 | "angular-gridster": "^0.13.14", | 27 | "angular-gridster": "^0.13.14", |
28 | "angular-hotkeys": "^1.7.0", | 28 | "angular-hotkeys": "^1.7.0", |
29 | "angular-jwt": "^0.1.6", | 29 | "angular-jwt": "^0.1.6", |
30 | - "angular-material": "1.1.9", | 30 | + "angular-material": "1.1.13", |
31 | "angular-material-data-table": "^0.10.9", | 31 | "angular-material-data-table": "^0.10.9", |
32 | "angular-material-expansion-panel": "^0.7.2", | 32 | "angular-material-expansion-panel": "^0.7.2", |
33 | "angular-material-icons": "^0.7.1", | 33 | "angular-material-icons": "^0.7.1", |
@@ -60,11 +60,14 @@ | @@ -60,11 +60,14 @@ | ||
60 | "jquery.terminal": "^1.5.0", | 60 | "jquery.terminal": "^1.5.0", |
61 | "js-beautify": "^1.6.4", | 61 | "js-beautify": "^1.6.4", |
62 | "json-schema-defaults": "^0.2.0", | 62 | "json-schema-defaults": "^0.2.0", |
63 | + "jstree": "^3.3.7", | ||
64 | + "jstree-bootstrap-theme": "^1.0.1", | ||
63 | "leaflet": "^1.0.3", | 65 | "leaflet": "^1.0.3", |
64 | "leaflet-providers": "^1.1.17", | 66 | "leaflet-providers": "^1.1.17", |
65 | "material-ui": "^0.16.1", | 67 | "material-ui": "^0.16.1", |
66 | "material-ui-number-input": "^5.0.16", | 68 | "material-ui-number-input": "^5.0.16", |
67 | "md-color-picker": "0.2.6", | 69 | "md-color-picker": "0.2.6", |
70 | + "md-date-range-picker": "^0.8.4", | ||
68 | "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5", | 71 | "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5", |
69 | "moment": "^2.15.0", | 72 | "moment": "^2.15.0", |
70 | "ngFlowchart": "git://github.com/thingsboard/ngFlowchart.git#master", | 73 | "ngFlowchart": "git://github.com/thingsboard/ngFlowchart.git#master", |
@@ -13,6 +13,9 @@ | @@ -13,6 +13,9 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | + | ||
17 | +import './settings-card.scss'; | ||
18 | + | ||
16 | /*@ngInject*/ | 19 | /*@ngInject*/ |
17 | export default function AdminController(adminService, toast, $scope, $rootScope, $state, $translate) { | 20 | export default function AdminController(adminService, toast, $scope, $rootScope, $state, $translate) { |
18 | 21 |
@@ -15,8 +15,8 @@ | @@ -15,8 +15,8 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div layout="row" width="100%" layout-wrap> | ||
19 | - <md-card flex-gt-sm="60" flex="100" style="height: 100%;"> | 18 | +<div> |
19 | + <md-card class="settings-card"> | ||
20 | <md-card-title> | 20 | <md-card-title> |
21 | <md-card-title-text> | 21 | <md-card-title-text> |
22 | <span translate class="md-headline">admin.general-settings</span> | 22 | <span translate class="md-headline">admin.general-settings</span> |
@@ -15,8 +15,8 @@ | @@ -15,8 +15,8 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div layout="row" width="100%" layout-wrap tb-help="'outgoingMailSettings'" help-container-id="help-container"> | ||
19 | - <md-card flex-gt-sm="60" flex="100" style="height: 100%;"> | 18 | +<div tb-help="'outgoingMailSettings'" help-container-id="help-container"> |
19 | + <md-card class="settings-card"> | ||
20 | <md-card-title> | 20 | <md-card-title> |
21 | <md-card-title-text layout="row"> | 21 | <md-card-title-text layout="row"> |
22 | <span translate class="md-headline">admin.outgoing-mail-settings</span> | 22 | <span translate class="md-headline">admin.outgoing-mail-settings</span> |