Commit 11f35a504bfbb343c2b83258acaf663a21df73b2

Authored by Vladyslav_Prykhodko
2 parents 0badd582 47e177c2

Merge remote-tracking branch 'origin/feacher/add_map_here' into feacher/add_map_here

# Conflicts:
#	application/src/main/data/json/system/widget_bundles/maps.json
Showing 100 changed files with 3574 additions and 189 deletions

Too many changes to show.

To preserve performance only 100 of 162 files are displayed.

... ... @@ -116,6 +116,22 @@
116 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 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 }
\ No newline at end of file
... ...
  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 +}
\ No newline at end of file
... ...
... ... @@ -48,11 +48,13 @@ import org.thingsboard.server.dao.alarm.AlarmService;
48 48 import org.thingsboard.server.dao.asset.AssetService;
49 49 import org.thingsboard.server.dao.attributes.AttributesService;
50 50 import org.thingsboard.server.dao.audit.AuditLogService;
  51 +import org.thingsboard.server.dao.cassandra.CassandraCluster;
51 52 import org.thingsboard.server.dao.customer.CustomerService;
52 53 import org.thingsboard.server.dao.dashboard.DashboardService;
53 54 import org.thingsboard.server.dao.device.DeviceService;
54 55 import org.thingsboard.server.dao.entityview.EntityViewService;
55 56 import org.thingsboard.server.dao.event.EventService;
  57 +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
56 58 import org.thingsboard.server.dao.relation.RelationService;
57 59 import org.thingsboard.server.dao.rule.RuleChainService;
58 60 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -308,6 +310,16 @@ public class ActorSystemContext {
308 310 @Getter
309 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 323 public ActorSystemContext() {
312 324 config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
313 325 }
... ...
... ... @@ -118,17 +118,23 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
118 118 this.rpcSubscriptions = new HashMap<>();
119 119 this.toDeviceRpcPendingMap = new HashMap<>();
120 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 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 140 void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
... ...
... ... @@ -44,10 +44,12 @@ import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
44 44 import org.thingsboard.server.dao.alarm.AlarmService;
45 45 import org.thingsboard.server.dao.asset.AssetService;
46 46 import org.thingsboard.server.dao.attributes.AttributesService;
  47 +import org.thingsboard.server.dao.cassandra.CassandraCluster;
47 48 import org.thingsboard.server.dao.customer.CustomerService;
48 49 import org.thingsboard.server.dao.dashboard.DashboardService;
49 50 import org.thingsboard.server.dao.device.DeviceService;
50 51 import org.thingsboard.server.dao.entityview.EntityViewService;
  52 +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
51 53 import org.thingsboard.server.dao.relation.RelationService;
52 54 import org.thingsboard.server.dao.rule.RuleChainService;
53 55 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -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 91 public void start(ActorContext context) {
92 92 if (!started) {
93 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 107 } else {
106 108 onUpdate(context);
107 109 }
... ... @@ -110,31 +112,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
110 112 @Override
111 113 public void onUpdate(ActorContext context) {
112 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 144 @Override
... ...
... ... @@ -55,7 +55,9 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
55 55 @Override
56 56 public void start(ActorContext context) throws Exception {
57 57 tbNode = initComponent(ruleNode);
58   - state = ComponentLifecycleState.ACTIVE;
  58 + if (tbNode != null) {
  59 + state = ComponentLifecycleState.ACTIVE;
  60 + }
59 61 }
60 62
61 63 @Override
... ... @@ -118,9 +120,12 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
118 120 }
119 121
120 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 129 return tbNode;
125 130 }
126 131
... ...
... ... @@ -22,7 +22,7 @@ import java.util.HashMap;
22 22 import java.util.Map;
23 23
24 24 @Configuration
25   -@ConfigurationProperties(prefix = "audit_log.logging_level")
  25 +@ConfigurationProperties(prefix = "audit-log.logging-level")
26 26 public class AuditLogLevelProperties {
27 27
28 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 15 */
16 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 26 import org.springframework.context.MessageSource;
19 27 import org.springframework.context.annotation.Bean;
20 28 import org.springframework.context.annotation.Configuration;
21 29 import org.springframework.context.annotation.Primary;
22 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 41 @Configuration
25 42 public class ThingsboardMessageConfiguration {
... ... @@ -32,5 +49,114 @@ public class ThingsboardMessageConfiguration {
32 49 messageSource.setDefaultEncoding("UTF-8");
33 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 57 @Configuration
58 58 @EnableWebSecurity
59 59 @EnableGlobalMethodSecurity(prePostEnabled=true)
60   -@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
  60 +@Order(SecurityProperties.BASIC_AUTH_ORDER)
61 61 public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter {
62 62
63 63 public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
... ...
... ... @@ -58,7 +58,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
58 58
59 59 @Override
60 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 62 SecurityUser user = null;
63 63 try {
64 64 user = getCurrentUser();
... ... @@ -73,7 +73,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
73 73
74 74 @Override
75 75 public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
76   - Exception exception) {
  76 + Exception exception) {
77 77 //Do nothing
78 78 }
79 79 });
... ...
... ... @@ -392,4 +392,4 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
392 392 }
393 393 }
394 394
395   -}
  395 +}
\ No newline at end of file
... ...
... ... @@ -124,6 +124,7 @@ public class ThingsboardInstallService {
124 124 systemDataLoaderService.deleteSystemWidgetBundle("maps_v2");
125 125 systemDataLoaderService.deleteSystemWidgetBundle("gateway_widgets");
126 126 systemDataLoaderService.deleteSystemWidgetBundle("input_widgets");
  127 + systemDataLoaderService.deleteSystemWidgetBundle("date");
127 128
128 129 systemDataLoaderService.loadSystemWidgets();
129 130 break;
... ...
... ... @@ -52,6 +52,8 @@ import javax.annotation.PostConstruct;
52 52 import javax.annotation.PreDestroy;
53 53 import java.util.List;
54 54 import java.util.NoSuchElementException;
  55 +import java.util.concurrent.ExecutorService;
  56 +import java.util.concurrent.Executors;
55 57 import java.util.stream.Collectors;
56 58
57 59 import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
... ... @@ -96,11 +98,13 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
96 98 @Lazy
97 99 private ClusterRoutingService routingService;
98 100
  101 + private ExecutorService reconnectExecutorService;
  102 +
99 103 private CuratorFramework client;
100 104 private PathChildrenCache cache;
101 105 private String nodePath;
102 106
103   - private volatile boolean stopped = false;
  107 + private volatile boolean stopped = true;
104 108
105 109 @PostConstruct
106 110 public void init() {
... ... @@ -110,9 +114,15 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
110 114 Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
111 115 Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
112 116
  117 + reconnectExecutorService = Executors.newSingleThreadExecutor();
  118 +
113 119 log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
114 120
115 121 zkNodesDir = zkDir + "/nodes";
  122 + initZkClient();
  123 + }
  124 +
  125 + private void initZkClient() {
116 126 try {
117 127 client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval));
118 128 client.start();
... ... @@ -120,6 +130,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
120 130 cache = new PathChildrenCache(client, zkNodesDir, true);
121 131 cache.getListenable().addListener(this);
122 132 cache.start();
  133 + stopped = false;
  134 + log.info("ZK client connected");
123 135 } catch (Exception e) {
124 136 log.error("Failed to connect to ZK: {}", e.getMessage(), e);
125 137 CloseableUtils.closeQuietly(cache);
... ... @@ -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 144 stopped = true;
134   - unpublishCurrentServer();
  145 + try {
  146 + unpublishCurrentServer();
  147 + } catch (Exception e) {}
135 148 CloseableUtils.closeQuietly(cache);
136 149 CloseableUtils.closeQuietly(client);
  150 + log.info("ZK client disconnected");
  151 + }
  152 +
  153 + @PreDestroy
  154 + public void destroy() {
  155 + destroyZkClient();
  156 + reconnectExecutorService.shutdownNow();
137 157 log.info("Stopped discovery service");
138 158 }
139 159
... ... @@ -180,20 +200,21 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
180 200 return (client, newState) -> {
181 201 log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
182 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 210 private synchronized void reconnect() {
191 211 if (!reconnectInProgress) {
192 212 reconnectInProgress = true;
193 213 try {
194   - client.blockUntilConnected();
  214 + destroyZkClient();
  215 + initZkClient();
195 216 publishCurrentServer();
196   - } catch (InterruptedException e) {
  217 + } catch (Exception e) {
197 218 log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
198 219 } finally {
199 220 reconnectInProgress = false;
... ...
... ... @@ -95,15 +95,24 @@ public final class GrpcSession implements Closeable {
95 95 }
96 96
97 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 113 @Override
106 114 public void close() {
  115 + connected = false;
107 116 try {
108 117 outputStream.onCompleted();
109 118 } catch (IllegalStateException e) {
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.service.install;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.beans.factory.annotation.Qualifier;
20 21 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
21 22 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
22 23
... ... @@ -30,6 +31,7 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database
30 31 private static final String CASSANDRA_DIR = "cassandra";
31 32
32 33 @Autowired
  34 + @Qualifier("CassandraInstallCluster")
33 35 private CassandraInstallCluster cluster;
34 36
35 37 @Autowired
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.service.install;
18 18 import com.datastax.driver.core.KeyspaceMetadata;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.beans.factory.annotation.Qualifier;
21 22 import org.springframework.context.annotation.Profile;
22 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.dao.cassandra.CassandraCluster;
... ... @@ -65,6 +66,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
65 66 private CassandraCluster cluster;
66 67
67 68 @Autowired
  69 + @Qualifier("CassandraInstallCluster")
68 70 private CassandraInstallCluster installCluster;
69 71
70 72 @Autowired
... ...
... ... @@ -18,7 +18,9 @@ package org.thingsboard.server.service.mail;
18 18 import com.fasterxml.jackson.databind.JsonNode;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.apache.commons.lang3.StringUtils;
  21 +import org.apache.velocity.VelocityContext;
21 22 import org.apache.velocity.app.VelocityEngine;
  23 +import org.apache.velocity.exception.VelocityException;
22 24 import org.springframework.beans.factory.annotation.Autowired;
23 25 import org.springframework.beans.factory.annotation.Qualifier;
24 26 import org.springframework.context.MessageSource;
... ... @@ -26,7 +28,6 @@ import org.springframework.core.NestedRuntimeException;
26 28 import org.springframework.mail.javamail.JavaMailSenderImpl;
27 29 import org.springframework.mail.javamail.MimeMessageHelper;
28 30 import org.springframework.stereotype.Service;
29   -import org.springframework.ui.velocity.VelocityEngineUtils;
30 31 import org.thingsboard.rule.engine.api.MailService;
31 32 import org.thingsboard.server.common.data.AdminSettings;
32 33 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
... ... @@ -39,6 +40,8 @@ import org.thingsboard.server.dao.settings.AdminSettingsService;
39 40 import javax.annotation.PostConstruct;
40 41 import javax.mail.MessagingException;
41 42 import javax.mail.internet.MimeMessage;
  43 +import java.io.StringWriter;
  44 +import java.io.Writer;
42 45 import java.util.HashMap;
43 46 import java.util.Locale;
44 47 import java.util.Map;
... ... @@ -126,7 +129,7 @@ public class DefaultMailService implements MailService {
126 129 Map<String, Object> model = new HashMap<String, Object>();
127 130 model.put(TARGET_EMAIL, email);
128 131
129   - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
  132 + String message = mergeTemplateIntoString(this.engine,
130 133 "test.vm", UTF_8, model);
131 134
132 135 sendMail(testMailSender, mailFrom, email, subject, message);
... ... @@ -141,7 +144,7 @@ public class DefaultMailService implements MailService {
141 144 model.put("activationLink", activationLink);
142 145 model.put(TARGET_EMAIL, email);
143 146
144   - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
  147 + String message = mergeTemplateIntoString(this.engine,
145 148 "activation.vm", UTF_8, model);
146 149
147 150 sendMail(mailSender, mailFrom, email, subject, message);
... ... @@ -156,7 +159,7 @@ public class DefaultMailService implements MailService {
156 159 model.put("loginLink", loginLink);
157 160 model.put(TARGET_EMAIL, email);
158 161
159   - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
  162 + String message = mergeTemplateIntoString(this.engine,
160 163 "account.activated.vm", UTF_8, model);
161 164
162 165 sendMail(mailSender, mailFrom, email, subject, message);
... ... @@ -171,7 +174,7 @@ public class DefaultMailService implements MailService {
171 174 model.put("passwordResetLink", passwordResetLink);
172 175 model.put(TARGET_EMAIL, email);
173 176
174   - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
  177 + String message = mergeTemplateIntoString(this.engine,
175 178 "reset.password.vm", UTF_8, model);
176 179
177 180 sendMail(mailSender, mailFrom, email, subject, message);
... ... @@ -186,7 +189,7 @@ public class DefaultMailService implements MailService {
186 189 model.put("loginLink", loginLink);
187 190 model.put(TARGET_EMAIL, email);
188 191
189   - String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
  192 + String message = mergeTemplateIntoString(this.engine,
190 193 "password.was.reset.vm", UTF_8, model);
191 194
192 195 sendMail(mailSender, mailFrom, email, subject, message);
... ... @@ -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 247 protected ThingsboardException handleException(Exception exception) {
229 248 String message;
230 249 if (exception instanceof NestedRuntimeException) {
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.RpcError;
28 28 import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
29 29 import org.thingsboard.server.actors.service.ActorService;
30 30 import org.thingsboard.server.common.data.DataConstants;
  31 +import org.thingsboard.server.common.data.Device;
31 32 import org.thingsboard.server.common.data.id.DeviceId;
32 33 import org.thingsboard.server.common.data.id.TenantId;
33 34 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -38,6 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
38 39 import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
39 40 import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
40 41 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  42 +import org.thingsboard.server.dao.device.DeviceService;
41 43 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
42 44 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
43 45 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
... ... @@ -68,6 +70,9 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
68 70 private ClusterRpcService rpcService;
69 71
70 72 @Autowired
  73 + private DeviceService deviceService;
  74 +
  75 + @Autowired
71 76 @Lazy
72 77 private ActorService actorService;
73 78
... ... @@ -171,6 +176,12 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
171 176 metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime()));
172 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 185 entityNode.put("method", msg.getBody().getMethod());
175 186 entityNode.put("params", msg.getBody().getParams());
176 187
... ...
... ... @@ -47,11 +47,4 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi
47 47 log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions);
48 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 27 import org.springframework.stereotype.Service;
28 28 import org.springframework.util.StringUtils;
29 29 import org.springframework.web.socket.CloseStatus;
30   -import org.springframework.web.socket.WebSocketSession;
31 30 import org.thingsboard.server.common.data.DataConstants;
32 31 import org.thingsboard.server.common.data.id.CustomerId;
33 32 import org.thingsboard.server.common.data.id.EntityId;
... ...
... ... @@ -273,31 +273,27 @@ updates:
273 273 spring.mvc.cors:
274 274 mappings:
275 275 # Intercept path
276   - "/api/auth/**":
  276 + "[/api/**]":
277 277 #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled.
278 278 allowed-origins: "*"
279 279 #Comma-separated list of methods to allow. '*' allows all methods.
280   - allowed-methods: "POST,GET,OPTIONS"
  280 + allowed-methods: "*"
281 281 #Comma-separated list of headers to allow in a request. '*' allows all headers.
282 282 allowed-headers: "*"
283 283 #How long, in seconds, the response from a pre-flight request can be cached by clients.
284 284 max-age: "1800"
285 285 #Set whether credentials are supported. When not set, credentials are not supported.
286 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 288 # spring serve gzip compressed static resources
295 289 spring.resources.chain:
296   - gzipped: "true"
  290 + compressed: "true"
297 291 strategy:
298 292 content:
299 293 enabled: "true"
300 294
  295 +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
  296 +
301 297 # HSQLDB DAO Configuration
302 298 spring:
303 299 data:
... ... @@ -331,7 +327,7 @@ spring:
331 327 # password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
332 328
333 329 # Audit log parameters
334   -audit_log:
  330 +audit-log:
335 331 # Enable/disable audit log functionality.
336 332 enabled: "${AUDIT_LOG_ENABLED:true}"
337 333 # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS
... ... @@ -340,7 +336,7 @@ audit_log:
340 336 default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}"
341 337 # Logging levels per each entity type.
342 338 # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations)
343   - logging_level:
  339 + logging-level:
344 340 mask:
345 341 "device": "${AUDIT_LOG_MASK_DEVICE:W}"
346 342 "asset": "${AUDIT_LOG_MASK_ASSET:W}"
... ...
... ... @@ -128,7 +128,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
128 128 String accessToken = deviceCredentials.getCredentialsId();
129 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 132 String deviceId = savedDevice.getId().getId().toString();
133 133
134 134 doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),
... ... @@ -183,7 +183,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
183 183 String accessToken = deviceCredentials.getCredentialsId();
184 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 187 String deviceId = savedDevice.getId().getId().toString();
188 188
189 189 doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),
... ...
... ... @@ -111,7 +111,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
111 111 client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
112 112 String payload = "{\"key\":\"value\"}";
113 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 115 assertEquals(payload, callback.getPayload());
116 116 assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
117 117 }
... ...
... ... @@ -139,11 +139,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
139 139 }
140 140
141 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 143 if (!StringUtils.isEmpty(responseMsg.getError())) {
144 144 throw new AdaptorException(responseMsg.getError());
145 145 } else {
146   - JsonObject result = JsonConverter.getJsonObjectForGateway(responseMsg);
  146 + JsonObject result = JsonConverter.getJsonObjectForGateway(deviceName, responseMsg);
147 147 return Optional.of(createMqttPublishMsg(ctx, MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, result));
148 148 }
149 149 }
... ...
... ... @@ -48,7 +48,7 @@ public interface MqttTransportAdaptor {
48 48
49 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 53 Optional<MqttMessage> convertToPublish(MqttDeviceAwareSessionContext ctx, AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException;
54 54
... ...
... ... @@ -65,7 +65,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple
65 65 @Override
66 66 public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg response) {
67 67 try {
68   - parent.getAdaptor().convertToGatewayPublish(this, response).ifPresent(parent::writeAndFlush);
  68 + parent.getAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), response).ifPresent(parent::writeAndFlush);
69 69 } catch (Exception e) {
70 70 log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e);
71 71 }
... ...
... ... @@ -279,10 +279,10 @@ public class GatewaySessionHandler {
279 279
280 280 @Override
281 281 public void onFailure(Throwable t) {
  282 + ack(msg);
282 283 log.debug("[{}] Failed to process device attributes request command: {}", sessionId, deviceName, t);
283 284 }
284 285 }, context.getExecutor());
285   - ack(msg);
286 286 } else {
287 287 throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
288 288 }
... ...
... ... @@ -257,9 +257,10 @@ public class JsonConverter {
257 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 261 JsonObject result = new JsonObject();
262 262 result.addProperty("id", responseMsg.getRequestId());
  263 + result.addProperty(DEVICE_PROPERTY, deviceName);
263 264 if (responseMsg.getClientAttributeListCount() > 0) {
264 265 addValues(result, responseMsg.getClientAttributeListList());
265 266 }
... ...
... ... @@ -18,11 +18,7 @@ package org.thingsboard.server.dao;
18 18 import org.thingsboard.server.common.data.id.UUIDBased;
19 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 23 public abstract class DaoUtil {
28 24
... ... @@ -50,6 +46,14 @@ public abstract class DaoUtil {
50 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 57 public static UUID getId(UUIDBased idBased) {
54 58 UUID id = null;
55 59 if (idBased != null) {
... ...
... ... @@ -60,7 +60,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
60 60
61 61 @Slf4j
62 62 @Service
63   -@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
  63 +@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true")
64 64 public class AuditLogServiceImpl implements AuditLogService {
65 65
66 66 private static final ObjectMapper objectMapper = new ObjectMapper();
... ...
... ... @@ -88,11 +88,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
88 88
89 89 protected ExecutorService readResultsProcessingExecutor;
90 90
91   - @Value("${audit_log.by_tenant_partitioning}")
  91 + @Value("${audit-log.by_tenant_partitioning}")
92 92 private String partitioning;
93 93 private TsPartitionDate tsFormat;
94 94
95   - @Value("${audit_log.default_query_period}")
  95 + @Value("${audit-log.default_query_period}")
96 96 private Integer defaultQueryPeriodInDays;
97 97
98 98 private PreparedStatement partitionInsertStmt;
... ...
... ... @@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
33 33 import java.util.List;
34 34
35 35 @Service
36   -@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false")
  36 +@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "false")
37 37 public class DummyAuditLogServiceImpl implements AuditLogService {
38 38
39 39 @Override
... ...
... ... @@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
20 20 import org.thingsboard.server.common.data.audit.AuditLog;
21 21
22 22 @Component
23   -@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "none")
  23 +@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "none")
24 24 public class DummyAuditLogSink implements AuditLogSink {
25 25
26 26 @Override
... ...
... ... @@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter;
44 44 import java.util.Collections;
45 45
46 46 @Component
47   -@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "elasticsearch")
  47 +@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "elasticsearch")
48 48 @Slf4j
49 49 public class ElasticsearchAuditLogSink implements AuditLogSink {
50 50
... ... @@ -54,19 +54,19 @@ public class ElasticsearchAuditLogSink implements AuditLogSink {
54 54
55 55 private final ObjectMapper mapper = new ObjectMapper();
56 56
57   - @Value("${audit_log.sink.index_pattern}")
  57 + @Value("${audit-log.sink.index_pattern}")
58 58 private String indexPattern;
59   - @Value("${audit_log.sink.scheme_name}")
  59 + @Value("${audit-log.sink.scheme_name}")
60 60 private String schemeName;
61   - @Value("${audit_log.sink.host}")
  61 + @Value("${audit-log.sink.host}")
62 62 private String host;
63   - @Value("${audit_log.sink.port}")
  63 + @Value("${audit-log.sink.port}")
64 64 private int port;
65   - @Value("${audit_log.sink.user_name}")
  65 + @Value("${audit-log.sink.user_name}")
66 66 private String userName;
67   - @Value("${audit_log.sink.password}")
  67 + @Value("${audit-log.sink.password}")
68 68 private String password;
69   - @Value("${audit_log.sink.date_format}")
  69 + @Value("${audit-log.sink.date_format}")
70 70 private String dateFormat;
71 71
72 72 private RestClient restClient;
... ...
... ... @@ -19,14 +19,37 @@ import lombok.Data;
19 19 import org.springframework.beans.factory.annotation.Value;
20 20 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
21 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 27 import org.springframework.cache.annotation.EnableCaching;
23 28 import org.springframework.cache.interceptor.KeyGenerator;
24 29 import org.springframework.context.annotation.Bean;
25 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 35 import org.springframework.data.redis.cache.RedisCacheManager;
27 36 import org.springframework.data.redis.connection.RedisConnectionFactory;
28 37 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
29 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 54 @Configuration
32 55 @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false)
... ... @@ -57,15 +80,12 @@ public class TBRedisCacheConfiguration {
57 80 }
58 81
59 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 91 @Bean
... ... @@ -73,5 +93,8 @@ public class TBRedisCacheConfiguration {
73 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 21
22 22 import javax.annotation.PostConstruct;
23 23
24   -@Component
  24 +@Component("CassandraCluster")
25 25 @NoSqlAnyDao
26 26 public class CassandraCluster extends AbstractCassandraCluster {
27 27
... ...
... ... @@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao;
21 21
22 22 import javax.annotation.PostConstruct;
23 23
24   -@Component
  24 +@Component("CassandraInstallCluster")
25 25 @NoSqlAnyDao
26 26 @Profile("install")
27 27 public class CassandraInstallCluster extends AbstractCassandraCluster {
... ...
... ... @@ -140,7 +140,7 @@ public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntit
140 140 auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString()));
141 141 }
142 142 if (userId != null) {
143   - auditLog.setUserId(new UserId(toUUID(entityId)));
  143 + auditLog.setUserId(new UserId(toUUID(userId)));
144 144 }
145 145 auditLog.setEntityName(this.entityName);
146 146 auditLog.setUserName(this.userName);
... ...
... ... @@ -27,6 +27,7 @@ import com.datastax.driver.core.TypeCodec;
27 27 import com.datastax.driver.core.exceptions.CodecNotFoundException;
28 28 import lombok.extern.slf4j.Slf4j;
29 29 import org.springframework.beans.factory.annotation.Autowired;
  30 +import org.springframework.beans.factory.annotation.Qualifier;
30 31 import org.thingsboard.server.common.data.id.TenantId;
31 32 import org.thingsboard.server.dao.cassandra.CassandraCluster;
32 33 import org.thingsboard.server.dao.model.type.AuthorityCodec;
... ... @@ -44,6 +45,7 @@ import java.util.concurrent.ConcurrentMap;
44 45 public abstract class CassandraAbstractDao {
45 46
46 47 @Autowired
  48 + @Qualifier("CassandraCluster")
47 49 protected CassandraCluster cluster;
48 50
49 51 private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>();
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.dao.DaoUtil;
27 27 import org.thingsboard.server.dao.model.BaseEntity;
28 28
29 29 import java.util.List;
  30 +import java.util.Optional;
30 31 import java.util.UUID;
31 32
32 33 import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
... ... @@ -67,23 +68,23 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D>
67 68 @Override
68 69 public D findById(TenantId tenantId, UUID key) {
69 70 log.debug("Get entity by key {}", key);
70   - E entity = getCrudRepository().findOne(fromTimeUUID(key));
  71 + Optional<E> entity = getCrudRepository().findById(fromTimeUUID(key));
71 72 return DaoUtil.getData(entity);
72 73 }
73 74
74 75 @Override
75 76 public ListenableFuture<D> findByIdAsync(TenantId tenantId, UUID key) {
76 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 81 @Override
81 82 @Transactional
82 83 public boolean removeById(TenantId tenantId, UUID id) {
83 84 String key = fromTimeUUID(id);
84   - getCrudRepository().delete(key);
  85 + getCrudRepository().deleteById(key);
85 86 log.debug("Remove request: {}", key);
86   - return getCrudRepository().findOne(key) == null;
  87 + return !getCrudRepository().existsById(key);
87 88 }
88 89
89 90 @Override
... ...
... ... @@ -52,7 +52,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
52 52 AttributeKvCompositeKey compositeKey =
53 53 getAttributeKvCompositeKey(entityId, attributeType, attributeKey);
54 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 58 @Override
... ... @@ -64,7 +64,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
64 64 getAttributeKvCompositeKey(entityId, attributeType, attributeKey))
65 65 .collect(Collectors.toList());
66 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 70 @Override
... ... @@ -103,7 +103,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
103 103 }).collect(Collectors.toList());
104 104
105 105 return service.submit(() -> {
106   - attributeKvRepository.delete(entitiesToDelete);
  106 + attributeKvRepository.deleteAll(entitiesToDelete);
107 107 return null;
108 108 });
109 109 }
... ...
... ... @@ -66,7 +66,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao<Comp
66 66 if (component.getId() == null) {
67 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 70 return Optional.of(save(tenantId, component));
71 71 }
72 72 return Optional.empty();
... ...
... ... @@ -97,13 +97,13 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
97 97 @Override
98 98 public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
99 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 103 @Override
104 104 public ListenableFuture<EntityRelation> getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
105 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 109 private RelationCompositeKey getRelationCompositeKey(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
... ... @@ -152,9 +152,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
152 152 }
153 153
154 154 private boolean deleteRelationIfExists(RelationCompositeKey key) {
155   - boolean relationExistsBeforeDelete = relationRepository.exists(key);
  155 + boolean relationExistsBeforeDelete = relationRepository.existsById(key);
156 156 if (relationExistsBeforeDelete) {
157   - relationRepository.delete(key);
  157 + relationRepository.deleteById(key);
158 158 }
159 159 return relationExistsBeforeDelete;
160 160 }
... ...
... ... @@ -53,7 +53,7 @@ public interface RelationRepository
53 53 RelationEntity save(RelationEntity entity);
54 54
55 55 @Transactional
56   - void delete(RelationCompositeKey id);
  56 + void deleteById(RelationCompositeKey id);
57 57
58 58 @Transactional
59 59 void deleteByFromIdAndFromType(String fromId, String fromType);
... ...
... ... @@ -284,10 +284,10 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
284 284 entityId.getEntityType(),
285 285 fromTimeUUID(entityId.getId()),
286 286 key);
287   - TsKvLatestEntity entry = tsKvLatestRepository.findOne(compositeKey);
  287 + Optional<TsKvLatestEntity> entry = tsKvLatestRepository.findById(compositeKey);
288 288 TsKvEntry result;
289   - if (entry != null) {
290   - result = DaoUtil.getData(entry);
  289 + if (entry.isPresent()) {
  290 + result = DaoUtil.getData(entry.get());
291 291 } else {
292 292 result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null));
293 293 }
... ...
... ... @@ -82,6 +82,7 @@ public class CustomCassandraCQLUnit extends BaseCassandraUnit {
82 82 session = null;
83 83 cluster = null;
84 84 }
  85 + System.setSecurityManager(null);
85 86 }
86 87
87 88 // Getters for those who do not like to directly access fields
... ...
... ... @@ -4,10 +4,10 @@ zk.zk_dir=/thingsboard
4 4
5 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 12 cache.type=caffeine
13 13 #cache.type=redis
... ...
... ... @@ -4,6 +4,7 @@ database.entities.type=sql
4 4 sql.ts_inserts_executor_type=fixed
5 5 sql.ts_inserts_fixed_thread_pool_size=10
6 6
  7 +spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
7 8 spring.jpa.show-sql=false
8 9 spring.jpa.hibernate.ddl-auto=validate
9 10 spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect
... ...
  1 +
  2 +# Database used by ThingsBoard, can be either postgres (PostgreSQL) or cassandra (Cassandra).
  3 +# According to the database type corresponding kubernetes resources will be deployed (see postgres.yml, cassandra.yml for details).
  4 +
  5 +DATABASE=postgres
... ...
  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.
... ...
  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 +---
... ...
  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
... ...
  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
... ...
  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
... ...
  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
... ...
  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
... ...
  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
... ...
  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 +---
... ...
  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>
... ...
  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>
... ...
  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>
... ...
  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
... ...
  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"
... ...
  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>
... ...
  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
... ...
  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 +---
\ No newline at end of file
... ...
... ... @@ -185,21 +185,21 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
185 185
186 186 case AT_LEAST_ONCE:
187 187 invokeHandlersForIncomingPublish(message);
188   - if (message.variableHeader().messageId() != -1) {
  188 + if (message.variableHeader().packetId() != -1) {
189 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 191 channel.writeAndFlush(new MqttPubAckMessage(fixedHeader, variableHeader));
192 192 }
193 193 break;
194 194
195 195 case EXACTLY_ONCE:
196   - if (message.variableHeader().messageId() != -1) {
  196 + if (message.variableHeader().packetId() != -1) {
197 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 199 MqttMessage pubrecMessage = new MqttMessage(fixedHeader, variableHeader);
200 200
201 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 203 message.payload().retain();
204 204 incomingQos2Publish.startPubrecRetransmitTimer(this.client.getEventLoop().next(), this.client::sendAndFlushPacket);
205 205
... ... @@ -249,7 +249,7 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
249 249 MqttIncomingQos2Publish incomingQos2Publish = this.client.getQos2PendingIncomingPublishes().get(((MqttMessageIdVariableHeader) message.variableHeader()).messageId());
250 250 this.invokeHandlersForIncomingPublish(incomingQos2Publish.getIncomingPublish());
251 251 incomingQos2Publish.onPubrelReceived();
252   - this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().messageId());
  252 + this.client.getQos2PendingIncomingPublishes().remove(incomingQos2Publish.getIncomingPublish().variableHeader().packetId());
253 253 }
254 254 MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0);
255 255 MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(((MqttMessageIdVariableHeader) message.variableHeader()).messageId());
... ...
... ... @@ -339,7 +339,7 @@ final class MqttClientImpl implements MqttClient {
339 339 MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, false, qos, retain, 0);
340 340 MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId());
341 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 343 ChannelFuture channelFuture = this.sendAndFlushPacket(message);
344 344
345 345 if (channelFuture != null) {
... ...
... ... @@ -29,10 +29,10 @@
29 29
30 30 <properties>
31 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 36 <jedis.version>2.9.0</jedis.version>
37 37 <jjwt.version>0.7.0</jjwt.version>
38 38 <json-path.version>2.2.0</json-path.version>
... ... @@ -41,8 +41,8 @@
41 41 <logback.version>1.2.3</logback.version>
42 42 <mockito.version>1.9.5</mockito.version>
43 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 46 <takari-cpsuite.version>1.2.7</takari-cpsuite.version>
47 47 <guava.version>21.0</guava.version>
48 48 <caffeine.version>2.6.1</caffeine.version>
... ... @@ -50,7 +50,7 @@
50 50 <commons-validator.version>1.5.0</commons-validator.version>
51 51 <commons-io.version>2.5</commons-io.version>
52 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 54 <json-schema-validator.version>2.2.6</json-schema-validator.version>
55 55 <scala.version>2.11</scala.version>
56 56 <akka.version>2.4.2</akka.version>
... ... @@ -59,18 +59,20 @@
59 59 <velocity.version>1.7</velocity.version>
60 60 <velocity-tools.version>2.0</velocity-tools.version>
61 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 65 <lombok.version>1.16.18</lombok.version>
66 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 68 <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
69 69 <rabbitmq.version>4.8.0</rabbitmq.version>
70 70 <surfire.version>2.19.1</surfire.version>
71 71 <jar-plugin.version>3.0.2</jar-plugin.version>
72 72 <springfox-swagger.version>2.6.1</springfox-swagger.version>
73 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 76 <bouncycastle.version>1.56</bouncycastle.version>
75 77 <winsw.version>2.0.1</winsw.version>
76 78 <hsqldb.version>2.4.0</hsqldb.version>
... ... @@ -85,6 +87,8 @@
85 87 <kafka.version>2.0.0</kafka.version>
86 88 <bucket4j.version>4.1.1</bucket4j.version>
87 89 <fst.version>2.57</fst.version>
  90 + <antlr.version>2.7.7</antlr.version>
  91 + <snakeyaml.version>1.23</snakeyaml.version>
88 92 </properties>
89 93
90 94 <modules>
... ... @@ -513,6 +517,16 @@
513 517 </exclusions>
514 518 </dependency>
515 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 530 <groupId>com.rabbitmq</groupId>
517 531 <artifactId>amqp-client</artifactId>
518 532 <version>${rabbitmq.version}</version>
... ... @@ -601,6 +615,16 @@
601 615 <version>${jackson.version}</version>
602 616 </dependency>
603 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 628 <groupId>com.github.fge</groupId>
605 629 <artifactId>json-schema-validator</artifactId>
606 630 <version>${json-schema-validator.version}</version>
... ... @@ -794,12 +818,28 @@
794 818 <groupId>de.ruedigermoeller</groupId>
795 819 <artifactId>fst</artifactId>
796 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 827 </dependency>
798 828 <dependency>
799 829 <groupId>io.springfox.ui</groupId>
800 830 <artifactId>springfox-swagger-ui-rfc6570</artifactId>
801 831 <version>${springfox-swagger-ui-rfc6570.version}</version>
802 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 843 </dependencies>
804 844 </dependencyManagement>
805 845
... ...
... ... @@ -25,10 +25,12 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
25 25 import org.thingsboard.server.dao.alarm.AlarmService;
26 26 import org.thingsboard.server.dao.asset.AssetService;
27 27 import org.thingsboard.server.dao.attributes.AttributesService;
  28 +import org.thingsboard.server.dao.cassandra.CassandraCluster;
28 29 import org.thingsboard.server.dao.customer.CustomerService;
29 30 import org.thingsboard.server.dao.dashboard.DashboardService;
30 31 import org.thingsboard.server.dao.device.DeviceService;
31 32 import org.thingsboard.server.dao.entityview.EntityViewService;
  33 +import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
32 34 import org.thingsboard.server.dao.relation.RelationService;
33 35 import org.thingsboard.server.dao.rule.RuleChainService;
34 36 import org.thingsboard.server.dao.tenant.TenantService;
... ... @@ -111,4 +113,9 @@ public interface TbContext {
111 113
112 114 EventLoopGroup getSharedEventLoop();
113 115
  116 + CassandraCluster getCassandraCluster();
  117 +
  118 + CassandraBufferedRateExecutor getCassandraBufferedRateExecutor();
  119 +
  120 +
114 121 }
... ...
... ... @@ -98,6 +98,14 @@
98 98 <artifactId>bcpkix-jdk15on</artifactId>
99 99 </dependency>
100 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 109 <groupId>junit</groupId>
102 110 <artifactId>junit</artifactId>
103 111 <version>${junit.version}</version>
... ... @@ -142,10 +150,10 @@
142 150 <executable>true</executable>
143 151 <excludeDevtools>true</excludeDevtools>
144 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 157 <!--</embeddedLaunchScriptProperties>-->
150 158 </configuration>
151 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 +}
\ No newline at end of file
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
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:&apos;...&apos;}}" }\'>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>&nbsp</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:&apos;...&apos;}}" }\'>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>&nbsp</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 6 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...
... ... @@ -468,9 +468,9 @@
468 468 "integrity": "sha512-o+V/OzwNGpS30QmgP7DJWTdBJ2BMDut481qqB72sM0L59dkO6TNjRV7qubQCntGqGe98h9vObweQUVYTfEO4vg=="
469 469 },
470 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 475 "angular-material-data-table": {
476 476 "version": "0.10.10",
... ... @@ -5239,12 +5239,14 @@
5239 5239 "balanced-match": {
5240 5240 "version": "1.0.0",
5241 5241 "bundled": true,
5242   - "dev": true
  5242 + "dev": true,
  5243 + "optional": true
5243 5244 },
5244 5245 "brace-expansion": {
5245 5246 "version": "1.1.11",
5246 5247 "bundled": true,
5247 5248 "dev": true,
  5249 + "optional": true,
5248 5250 "requires": {
5249 5251 "balanced-match": "^1.0.0",
5250 5252 "concat-map": "0.0.1"
... ... @@ -5259,17 +5261,20 @@
5259 5261 "code-point-at": {
5260 5262 "version": "1.1.0",
5261 5263 "bundled": true,
5262   - "dev": true
  5264 + "dev": true,
  5265 + "optional": true
5263 5266 },
5264 5267 "concat-map": {
5265 5268 "version": "0.0.1",
5266 5269 "bundled": true,
5267   - "dev": true
  5270 + "dev": true,
  5271 + "optional": true
5268 5272 },
5269 5273 "console-control-strings": {
5270 5274 "version": "1.1.0",
5271 5275 "bundled": true,
5272   - "dev": true
  5276 + "dev": true,
  5277 + "optional": true
5273 5278 },
5274 5279 "core-util-is": {
5275 5280 "version": "1.0.2",
... ... @@ -5386,7 +5391,8 @@
5386 5391 "inherits": {
5387 5392 "version": "2.0.3",
5388 5393 "bundled": true,
5389   - "dev": true
  5394 + "dev": true,
  5395 + "optional": true
5390 5396 },
5391 5397 "ini": {
5392 5398 "version": "1.3.5",
... ... @@ -5398,6 +5404,7 @@
5398 5404 "version": "1.0.0",
5399 5405 "bundled": true,
5400 5406 "dev": true,
  5407 + "optional": true,
5401 5408 "requires": {
5402 5409 "number-is-nan": "^1.0.0"
5403 5410 }
... ... @@ -5412,6 +5419,7 @@
5412 5419 "version": "3.0.4",
5413 5420 "bundled": true,
5414 5421 "dev": true,
  5422 + "optional": true,
5415 5423 "requires": {
5416 5424 "brace-expansion": "^1.1.7"
5417 5425 }
... ... @@ -5419,12 +5427,14 @@
5419 5427 "minimist": {
5420 5428 "version": "0.0.8",
5421 5429 "bundled": true,
5422   - "dev": true
  5430 + "dev": true,
  5431 + "optional": true
5423 5432 },
5424 5433 "minipass": {
5425 5434 "version": "2.2.4",
5426 5435 "bundled": true,
5427 5436 "dev": true,
  5437 + "optional": true,
5428 5438 "requires": {
5429 5439 "safe-buffer": "^5.1.1",
5430 5440 "yallist": "^3.0.0"
... ... @@ -5443,6 +5453,7 @@
5443 5453 "version": "0.5.1",
5444 5454 "bundled": true,
5445 5455 "dev": true,
  5456 + "optional": true,
5446 5457 "requires": {
5447 5458 "minimist": "0.0.8"
5448 5459 }
... ... @@ -5523,7 +5534,8 @@
5523 5534 "number-is-nan": {
5524 5535 "version": "1.0.1",
5525 5536 "bundled": true,
5526   - "dev": true
  5537 + "dev": true,
  5538 + "optional": true
5527 5539 },
5528 5540 "object-assign": {
5529 5541 "version": "4.1.1",
... ... @@ -5535,6 +5547,7 @@
5535 5547 "version": "1.4.0",
5536 5548 "bundled": true,
5537 5549 "dev": true,
  5550 + "optional": true,
5538 5551 "requires": {
5539 5552 "wrappy": "1"
5540 5553 }
... ... @@ -5656,6 +5669,7 @@
5656 5669 "version": "1.0.2",
5657 5670 "bundled": true,
5658 5671 "dev": true,
  5672 + "optional": true,
5659 5673 "requires": {
5660 5674 "code-point-at": "^1.0.0",
5661 5675 "is-fullwidth-code-point": "^1.0.0",
... ... @@ -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 7708 "keycode": {
7679 7709 "version": "2.2.0",
7680 7710 "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
... ... @@ -8328,6 +8358,18 @@
8328 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 8373 "mdPickers": {
8332 8374 "version": "git://github.com/alenaksu/mdPickers.git#72592ae51c81a7260701055ea21870efa57fa7c8",
8333 8375 "from": "git://github.com/alenaksu/mdPickers.git#0.7.5"
... ...
... ... @@ -27,7 +27,7 @@
27 27 "angular-gridster": "^0.13.14",
28 28 "angular-hotkeys": "^1.7.0",
29 29 "angular-jwt": "^0.1.6",
30   - "angular-material": "1.1.9",
  30 + "angular-material": "1.1.13",
31 31 "angular-material-data-table": "^0.10.9",
32 32 "angular-material-expansion-panel": "^0.7.2",
33 33 "angular-material-icons": "^0.7.1",
... ... @@ -60,11 +60,14 @@
60 60 "jquery.terminal": "^1.5.0",
61 61 "js-beautify": "^1.6.4",
62 62 "json-schema-defaults": "^0.2.0",
  63 + "jstree": "^3.3.7",
  64 + "jstree-bootstrap-theme": "^1.0.1",
63 65 "leaflet": "^1.0.3",
64 66 "leaflet-providers": "^1.1.17",
65 67 "material-ui": "^0.16.1",
66 68 "material-ui-number-input": "^5.0.16",
67 69 "md-color-picker": "0.2.6",
  70 + "md-date-range-picker": "^0.8.4",
68 71 "mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5",
69 72 "moment": "^2.15.0",
70 73 "ngFlowchart": "git://github.com/thingsboard/ngFlowchart.git#master",
... ...
... ... @@ -13,6 +13,9 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
  17 +import './settings-card.scss';
  18 +
16 19 /*@ngInject*/
17 20 export default function AdminController(adminService, toast, $scope, $rootScope, $state, $translate) {
18 21
... ...
... ... @@ -15,8 +15,8 @@
15 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 20 <md-card-title>
21 21 <md-card-title-text>
22 22 <span translate class="md-headline">admin.general-settings</span>
... ...
... ... @@ -15,8 +15,8 @@
15 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 20 <md-card-title>
21 21 <md-card-title-text layout="row">
22 22 <span translate class="md-headline">admin.outgoing-mail-settings</span>
... ...
  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 +@import "../../scss/constants";
  18 +
  19 +md-card.settings-card {
  20 + @media (min-width: $layout-breakpoint-sm) {
  21 + width: 60%;
  22 + }
  23 +}
... ...