Commit 1affc60ace8015f38ab5d9c168dffded34245feb

Authored by Andrii Shvaika
2 parents 9aecb146 06f199b9

Merge branch 'master' of github.com:thingsboard/thingsboard

Showing 27 changed files with 212 additions and 41 deletions
@@ -55,7 +55,7 @@ @@ -55,7 +55,7 @@
55 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>", 55 "templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
56 "templateCss": "", 56 "templateCss": "",
57 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", 57 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
58 - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", 58 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
59 "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, rowData, ctx)\",\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, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", 59 "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, rowData, ctx)\",\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, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}",
60 "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,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" 60 "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,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
61 } 61 }
@@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AdminSettingsId; @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AdminSettingsId;
21 21
22 import com.fasterxml.jackson.databind.JsonNode; 22 import com.fasterxml.jackson.databind.JsonNode;
23 import org.thingsboard.server.common.data.id.DeviceId; 23 import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 import org.thingsboard.server.common.data.validation.NoXss; 25 import org.thingsboard.server.common.data.validation.NoXss;
25 26
26 @ApiModel 27 @ApiModel
@@ -29,6 +30,7 @@ public class AdminSettings extends BaseData<AdminSettingsId> { @@ -29,6 +30,7 @@ public class AdminSettings extends BaseData<AdminSettingsId> {
29 private static final long serialVersionUID = -7670322981725511892L; 30 private static final long serialVersionUID = -7670322981725511892L;
30 31
31 @NoXss 32 @NoXss
  33 + @Length(fieldName = "key")
32 private String key; 34 private String key;
33 private transient JsonNode jsonValue; 35 private transient JsonNode jsonValue;
34 36
@@ -68,10 +68,13 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage @@ -68,10 +68,13 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
68 @NoXss 68 @NoXss
69 @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true) 69 @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true)
70 private String fileName; 70 private String fileName;
  71 + @NoXss
  72 + @Length(fieldName = "contentType")
71 @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true) 73 @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true)
72 private String contentType; 74 private String contentType;
73 @ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", readOnly = true) 75 @ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", readOnly = true)
74 private ChecksumAlgorithm checksumAlgorithm; 76 private ChecksumAlgorithm checksumAlgorithm;
  77 + @Length(fieldName = "checksum", max = 1020)
75 @ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", readOnly = true) 78 @ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", readOnly = true)
76 private String checksum; 79 private String checksum;
77 @ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", readOnly = true) 80 @ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", readOnly = true)
@@ -42,6 +42,8 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has @@ -42,6 +42,8 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
42 private String title; 42 private String title;
43 @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true) 43 @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true)
44 private ResourceType resourceType; 44 private ResourceType resourceType;
  45 + @NoXss
  46 + @Length(fieldName = "resourceKey")
45 @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true) 47 @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true)
46 private String resourceKey; 48 private String resourceKey;
47 @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", readOnly = true) 49 @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", readOnly = true)
@@ -37,6 +37,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId { @@ -37,6 +37,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
37 @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A") 37 @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A")
38 private String title; 38 private String title;
39 @NoXss 39 @NoXss
  40 + @Length(fieldName = "region")
40 @ApiModelProperty(position = 5, value = "Geo region of the tenant", example = "North America") 41 @ApiModelProperty(position = 5, value = "Geo region of the tenant", example = "North America")
41 private String region; 42 private String region;
42 43
@@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.EdgeId; @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
29 import org.thingsboard.server.common.data.id.RuleChainId; 29 import org.thingsboard.server.common.data.id.RuleChainId;
30 import org.thingsboard.server.common.data.id.TenantId; 30 import org.thingsboard.server.common.data.id.TenantId;
31 import org.thingsboard.server.common.data.validation.Length; 31 import org.thingsboard.server.common.data.validation.Length;
  32 +import org.thingsboard.server.common.data.validation.NoXss;
32 33
33 @ApiModel 34 @ApiModel
34 @EqualsAndHashCode(callSuper = true) 35 @EqualsAndHashCode(callSuper = true)
@@ -41,15 +42,26 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H @@ -41,15 +42,26 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
41 private TenantId tenantId; 42 private TenantId tenantId;
42 private CustomerId customerId; 43 private CustomerId customerId;
43 private RuleChainId rootRuleChainId; 44 private RuleChainId rootRuleChainId;
  45 + @NoXss
44 @Length(fieldName = "name") 46 @Length(fieldName = "name")
45 private String name; 47 private String name;
  48 + @NoXss
46 @Length(fieldName = "type") 49 @Length(fieldName = "type")
47 private String type; 50 private String type;
  51 + @NoXss
48 @Length(fieldName = "label") 52 @Length(fieldName = "label")
49 private String label; 53 private String label;
  54 + @NoXss
  55 + @Length(fieldName = "routingKey")
50 private String routingKey; 56 private String routingKey;
  57 + @NoXss
  58 + @Length(fieldName = "secret")
51 private String secret; 59 private String secret;
  60 + @NoXss
  61 + @Length(fieldName = "edgeLicenseKey", max = 30)
52 private String edgeLicenseKey; 62 private String edgeLicenseKey;
  63 + @NoXss
  64 + @Length(fieldName = "cloudEndpoint")
53 private String cloudEndpoint; 65 private String cloudEndpoint;
54 66
55 public Edge() { 67 public Edge() {
@@ -21,6 +21,7 @@ import lombok.Builder; @@ -21,6 +21,7 @@ import lombok.Builder;
21 import lombok.Data; 21 import lombok.Data;
22 import lombok.EqualsAndHashCode; 22 import lombok.EqualsAndHashCode;
23 import lombok.ToString; 23 import lombok.ToString;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 25
25 @Builder(toBuilder = true) 26 @Builder(toBuilder = true)
26 @EqualsAndHashCode 27 @EqualsAndHashCode
@@ -28,21 +29,27 @@ import lombok.ToString; @@ -28,21 +29,27 @@ import lombok.ToString;
28 @ToString 29 @ToString
29 @ApiModel 30 @ApiModel
30 public class OAuth2BasicMapperConfig { 31 public class OAuth2BasicMapperConfig {
  32 + @Length(fieldName = "emailAttributeKey", max = 31)
31 @ApiModelProperty(value = "Email attribute key of OAuth2 principal attributes. " + 33 @ApiModelProperty(value = "Email attribute key of OAuth2 principal attributes. " +
32 "Must be specified for BASIC mapper type and cannot be specified for GITHUB type") 34 "Must be specified for BASIC mapper type and cannot be specified for GITHUB type")
33 private final String emailAttributeKey; 35 private final String emailAttributeKey;
  36 + @Length(fieldName = "firstNameAttributeKey", max = 31)
34 @ApiModelProperty(value = "First name attribute key") 37 @ApiModelProperty(value = "First name attribute key")
35 private final String firstNameAttributeKey; 38 private final String firstNameAttributeKey;
  39 + @Length(fieldName = "lastNameAttributeKey", max = 31)
36 @ApiModelProperty(value = "Last name attribute key") 40 @ApiModelProperty(value = "Last name attribute key")
37 private final String lastNameAttributeKey; 41 private final String lastNameAttributeKey;
38 @ApiModelProperty(value = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true) 42 @ApiModelProperty(value = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true)
39 private final TenantNameStrategyType tenantNameStrategy; 43 private final TenantNameStrategyType tenantNameStrategy;
  44 + @Length(fieldName = "tenantNamePattern")
40 @ApiModelProperty(value = "Tenant name pattern for CUSTOM naming strategy. " + 45 @ApiModelProperty(value = "Tenant name pattern for CUSTOM naming strategy. " +
41 "OAuth2 attributes in the pattern can be used by enclosing attribute key in '%{' and '}'", example = "%{email}") 46 "OAuth2 attributes in the pattern can be used by enclosing attribute key in '%{' and '}'", example = "%{email}")
42 private final String tenantNamePattern; 47 private final String tenantNamePattern;
  48 + @Length(fieldName = "customerNamePattern")
43 @ApiModelProperty(value = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " + 49 @ApiModelProperty(value = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " +
44 "customer name will be used to create or find existing customer in the platform and assign customerId to the user") 50 "customer name will be used to create or find existing customer in the platform and assign customerId to the user")
45 private final String customerNamePattern; 51 private final String customerNamePattern;
  52 + @Length(fieldName = "defaultDashboardName")
46 @ApiModelProperty(value = "Name of the tenant's dashboard to set as default dashboard for newly created user") 53 @ApiModelProperty(value = "Name of the tenant's dashboard to set as default dashboard for newly created user")
47 private final String defaultDashboardName; 54 private final String defaultDashboardName;
48 @ApiModelProperty(value = "Whether default dashboard should be open in full screen") 55 @ApiModelProperty(value = "Whether default dashboard should be open in full screen")
@@ -24,7 +24,9 @@ import lombok.ToString; @@ -24,7 +24,9 @@ import lombok.ToString;
24 import org.thingsboard.server.common.data.HasName; 24 import org.thingsboard.server.common.data.HasName;
25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
26 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; 26 import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
  27 +import org.thingsboard.server.common.data.validation.Length;
27 28
  29 +import javax.validation.Valid;
28 import java.util.List; 30 import java.util.List;
29 31
30 @EqualsAndHashCode(callSuper = true) 32 @EqualsAndHashCode(callSuper = true)
@@ -34,30 +36,41 @@ import java.util.List; @@ -34,30 +36,41 @@ import java.util.List;
34 @ApiModel 36 @ApiModel
35 public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName { 37 public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
36 38
  39 + @Length(fieldName = "providerId")
37 @ApiModelProperty(value = "OAuth2 provider identifier (e.g. its name)", required = true) 40 @ApiModelProperty(value = "OAuth2 provider identifier (e.g. its name)", required = true)
38 private String providerId; 41 private String providerId;
  42 + @Valid
39 @ApiModelProperty(value = "Default config for mapping OAuth2 log in response to platform entities") 43 @ApiModelProperty(value = "Default config for mapping OAuth2 log in response to platform entities")
40 private OAuth2MapperConfig mapperConfig; 44 private OAuth2MapperConfig mapperConfig;
  45 + @Length(fieldName = "authorizationUri")
41 @ApiModelProperty(value = "Default authorization URI of the OAuth2 provider") 46 @ApiModelProperty(value = "Default authorization URI of the OAuth2 provider")
42 private String authorizationUri; 47 private String authorizationUri;
  48 + @Length(fieldName = "accessTokenUri")
43 @ApiModelProperty(value = "Default access token URI of the OAuth2 provider") 49 @ApiModelProperty(value = "Default access token URI of the OAuth2 provider")
44 private String accessTokenUri; 50 private String accessTokenUri;
45 @ApiModelProperty(value = "Default OAuth scopes that will be requested from OAuth2 platform") 51 @ApiModelProperty(value = "Default OAuth scopes that will be requested from OAuth2 platform")
46 private List<String> scope; 52 private List<String> scope;
  53 + @Length(fieldName = "userInfoUri")
47 @ApiModelProperty(value = "Default user info URI of the OAuth2 provider") 54 @ApiModelProperty(value = "Default user info URI of the OAuth2 provider")
48 private String userInfoUri; 55 private String userInfoUri;
  56 + @Length(fieldName = "userNameAttributeName")
49 @ApiModelProperty(value = "Default name of the username attribute in OAuth2 provider log in response") 57 @ApiModelProperty(value = "Default name of the username attribute in OAuth2 provider log in response")
50 private String userNameAttributeName; 58 private String userNameAttributeName;
  59 + @Length(fieldName = "jwkSetUri")
51 @ApiModelProperty(value = "Default JSON Web Key URI of the OAuth2 provider") 60 @ApiModelProperty(value = "Default JSON Web Key URI of the OAuth2 provider")
52 private String jwkSetUri; 61 private String jwkSetUri;
  62 + @Length(fieldName = "clientAuthenticationMethod")
53 @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'") 63 @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'")
54 private String clientAuthenticationMethod; 64 private String clientAuthenticationMethod;
55 @ApiModelProperty(value = "Comment for OAuth2 provider") 65 @ApiModelProperty(value = "Comment for OAuth2 provider")
56 private String comment; 66 private String comment;
  67 + @Length(fieldName = "loginButtonIcon")
57 @ApiModelProperty(value = "Default log in button icon for OAuth2 provider") 68 @ApiModelProperty(value = "Default log in button icon for OAuth2 provider")
58 private String loginButtonIcon; 69 private String loginButtonIcon;
  70 + @Length(fieldName = "loginButtonLabel")
59 @ApiModelProperty(value = "Default OAuth2 provider label") 71 @ApiModelProperty(value = "Default OAuth2 provider label")
60 private String loginButtonLabel; 72 private String loginButtonLabel;
  73 + @Length(fieldName = "helpLink")
61 @ApiModelProperty(value = "Help link for OAuth2 provider") 74 @ApiModelProperty(value = "Help link for OAuth2 provider")
62 private String helpLink; 75 private String helpLink;
63 76
@@ -15,15 +15,22 @@ @@ -15,15 +15,22 @@
15 */ 15 */
16 package org.thingsboard.server.common.data.oauth2; 16 package org.thingsboard.server.common.data.oauth2;
17 17
18 -import lombok.*; 18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.ToString;
  22 +import org.thingsboard.server.common.data.validation.Length;
19 23
20 @Builder(toBuilder = true) 24 @Builder(toBuilder = true)
21 @EqualsAndHashCode 25 @EqualsAndHashCode
22 @Data 26 @Data
23 @ToString(exclude = {"password"}) 27 @ToString(exclude = {"password"})
24 public class OAuth2CustomMapperConfig { 28 public class OAuth2CustomMapperConfig {
  29 + @Length(fieldName = "url")
25 private final String url; 30 private final String url;
  31 + @Length(fieldName = "username")
26 private final String username; 32 private final String username;
  33 + @Length(fieldName = "password")
27 private final String password; 34 private final String password;
28 private final boolean sendToken; 35 private final boolean sendToken;
29 } 36 }
@@ -21,6 +21,8 @@ import lombok.Data; @@ -21,6 +21,8 @@ import lombok.Data;
21 import lombok.EqualsAndHashCode; 21 import lombok.EqualsAndHashCode;
22 import lombok.ToString; 22 import lombok.ToString;
23 23
  24 +import javax.validation.Valid;
  25 +
24 @Builder(toBuilder = true) 26 @Builder(toBuilder = true)
25 @EqualsAndHashCode 27 @EqualsAndHashCode
26 @Data 28 @Data
@@ -32,8 +34,10 @@ public class OAuth2MapperConfig { @@ -32,8 +34,10 @@ public class OAuth2MapperConfig {
32 private boolean activateUser; 34 private boolean activateUser;
33 @ApiModelProperty(value = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true) 35 @ApiModelProperty(value = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true)
34 private MapperType type; 36 private MapperType type;
  37 + @Valid
35 @ApiModelProperty(value = "Mapper config for BASIC and GITHUB mapper types") 38 @ApiModelProperty(value = "Mapper config for BASIC and GITHUB mapper types")
36 private OAuth2BasicMapperConfig basic; 39 private OAuth2BasicMapperConfig basic;
  40 + @Valid
37 @ApiModelProperty(value = "Mapper config for CUSTOM mapper type") 41 @ApiModelProperty(value = "Mapper config for CUSTOM mapper type")
38 private OAuth2CustomMapperConfig custom; 42 private OAuth2CustomMapperConfig custom;
39 } 43 }
@@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty; @@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty;
21 import lombok.*; 21 import lombok.*;
22 import org.thingsboard.server.common.data.SearchTextBased; 22 import org.thingsboard.server.common.data.SearchTextBased;
23 import org.thingsboard.server.common.data.id.ComponentDescriptorId; 23 import org.thingsboard.server.common.data.id.ComponentDescriptorId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 25
25 /** 26 /**
26 * @author Andrew Shvayka 27 * @author Andrew Shvayka
@@ -35,12 +36,14 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId> @@ -35,12 +36,14 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId>
35 @Getter @Setter private ComponentType type; 36 @Getter @Setter private ComponentType type;
36 @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", readOnly = true, allowableValues = "TENANT", example = "TENANT") 37 @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", readOnly = true, allowableValues = "TENANT", example = "TENANT")
37 @Getter @Setter private ComponentScope scope; 38 @Getter @Setter private ComponentScope scope;
  39 + @Length(fieldName = "name")
38 @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node") 40 @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node")
39 @Getter @Setter private String name; 41 @Getter @Setter private String name;
40 @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode") 42 @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode")
41 @Getter @Setter private String clazz; 43 @Getter @Setter private String clazz;
42 @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true) 44 @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true)
43 @Getter @Setter private transient JsonNode configurationDescriptor; 45 @Getter @Setter private transient JsonNode configurationDescriptor;
  46 + @Length(fieldName = "actions")
44 @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true) 47 @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true)
45 @Getter @Setter private String actions; 48 @Getter @Setter private String actions;
46 49
@@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.BaseData; @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.BaseData;
21 import org.thingsboard.server.common.data.HasTenantId; 21 import org.thingsboard.server.common.data.HasTenantId;
22 import org.thingsboard.server.common.data.id.TenantId; 22 import org.thingsboard.server.common.data.id.TenantId;
23 import org.thingsboard.server.common.data.id.WidgetTypeId; 23 import org.thingsboard.server.common.data.id.WidgetTypeId;
  24 +import org.thingsboard.server.common.data.validation.Length;
24 import org.thingsboard.server.common.data.validation.NoXss; 25 import org.thingsboard.server.common.data.validation.NoXss;
25 26
26 @Data 27 @Data
@@ -31,12 +32,15 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantI @@ -31,12 +32,15 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantI
31 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) 32 @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true)
32 private TenantId tenantId; 33 private TenantId tenantId;
33 @NoXss 34 @NoXss
  35 + @Length(fieldName = "bundleAlias")
34 @ApiModelProperty(position = 4, value = "Reference to widget bundle", readOnly = true) 36 @ApiModelProperty(position = 4, value = "Reference to widget bundle", readOnly = true)
35 private String bundleAlias; 37 private String bundleAlias;
36 @NoXss 38 @NoXss
  39 + @Length(fieldName = "alias")
37 @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", readOnly = true) 40 @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", readOnly = true)
38 private String alias; 41 private String alias;
39 @NoXss 42 @NoXss
  43 + @Length(fieldName = "name")
40 @ApiModelProperty(position = 6, value = "Widget name used in search and UI", readOnly = true) 44 @ApiModelProperty(position = 6, value = "Widget name used in search and UI", readOnly = true)
41 private String name; 45 private String name;
42 46
@@ -19,15 +19,18 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder; @@ -19,15 +19,18 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
19 import io.swagger.annotations.ApiModelProperty; 19 import io.swagger.annotations.ApiModelProperty;
20 import lombok.Data; 20 import lombok.Data;
21 import org.thingsboard.server.common.data.id.WidgetTypeId; 21 import org.thingsboard.server.common.data.id.WidgetTypeId;
  22 +import org.thingsboard.server.common.data.validation.Length;
22 import org.thingsboard.server.common.data.validation.NoXss; 23 import org.thingsboard.server.common.data.validation.NoXss;
23 24
24 @Data 25 @Data
25 @JsonPropertyOrder({ "alias", "name", "image", "description", "descriptor" }) 26 @JsonPropertyOrder({ "alias", "name", "image", "description", "descriptor" })
26 public class WidgetTypeDetails extends WidgetType { 27 public class WidgetTypeDetails extends WidgetType {
27 28
  29 + @NoXss
28 @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", readOnly = true) 30 @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", readOnly = true)
29 private String image; 31 private String image;
30 @NoXss 32 @NoXss
  33 + @Length(fieldName = "description")
31 @ApiModelProperty(position = 9, value = "Description of the widget", readOnly = true) 34 @ApiModelProperty(position = 9, value = "Description of the widget", readOnly = true)
32 private String description; 35 private String description;
33 36
@@ -34,10 +34,9 @@ public class CoapEfentoCallback implements TransportServiceCallback<Void> { @@ -34,10 +34,9 @@ public class CoapEfentoCallback implements TransportServiceCallback<Void> {
34 34
35 @Override 35 @Override
36 public void onSuccess(Void msg) { 36 public void onSuccess(Void msg) {
  37 + //We respond only to confirmed requests in order to reduce battery consumption for Efento devices.
37 if (isConRequest()) { 38 if (isConRequest()) {
38 - Response response = new Response(onSuccessResponse);  
39 - response.setAcknowledged(true);  
40 - exchange.respond(response); 39 + exchange.respond(new Response(onSuccessResponse));
41 } 40 }
42 } 41 }
43 42
@@ -99,10 +99,9 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource { @@ -99,10 +99,9 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource {
99 break; 99 break;
100 case DEVICE_INFO: 100 case DEVICE_INFO:
101 case CONFIGURATION: 101 case CONFIGURATION:
102 - Response response = new Response(CoAP.ResponseCode.CREATED); 102 + //We respond only to confirmed requests in order to reduce battery consumption for Efento devices.
103 if (exchange.advanced().getRequest().isConfirmable()) { 103 if (exchange.advanced().getRequest().isConfirmable()) {
104 - response.setAcknowledged(true);  
105 - exchange.respond(response); 104 + exchange.respond(new Response(CoAP.ResponseCode.CREATED));
106 } 105 }
107 break; 106 break;
108 default: 107 default:
@@ -157,7 +157,11 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple @@ -157,7 +157,11 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
157 private boolean deleteRelationIfExists(RelationCompositeKey key) { 157 private boolean deleteRelationIfExists(RelationCompositeKey key) {
158 boolean relationExistsBeforeDelete = relationRepository.existsById(key); 158 boolean relationExistsBeforeDelete = relationRepository.existsById(key);
159 if (relationExistsBeforeDelete) { 159 if (relationExistsBeforeDelete) {
160 - relationRepository.deleteById(key); 160 + try {
  161 + relationRepository.deleteById(key);
  162 + } catch (ConcurrencyFailureException e) {
  163 + log.debug("[{}] Concurrency exception while deleting relation", key, e);
  164 + }
161 } 165 }
162 return relationExistsBeforeDelete; 166 return relationExistsBeforeDelete;
163 } 167 }
@@ -16,6 +16,8 @@ @@ -16,6 +16,8 @@
16 package org.thingsboard.server.dao.service; 16 package org.thingsboard.server.dao.service;
17 17
18 import com.datastax.oss.driver.api.core.uuid.Uuids; 18 import com.datastax.oss.driver.api.core.uuid.Uuids;
  19 +import com.google.common.util.concurrent.Futures;
  20 +import com.google.common.util.concurrent.ListenableFuture;
19 import org.junit.After; 21 import org.junit.After;
20 import org.junit.Assert; 22 import org.junit.Assert;
21 import org.junit.Before; 23 import org.junit.Before;
@@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
31 import org.thingsboard.server.common.data.relation.RelationsSearchParameters; 33 import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
32 import org.thingsboard.server.dao.exception.DataValidationException; 34 import org.thingsboard.server.dao.exception.DataValidationException;
33 35
  36 +import java.util.ArrayList;
34 import java.util.Collections; 37 import java.util.Collections;
35 import java.util.List; 38 import java.util.List;
36 import java.util.concurrent.ExecutionException; 39 import java.util.concurrent.ExecutionException;
@@ -85,6 +88,23 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { @@ -85,6 +88,23 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
85 } 88 }
86 89
87 @Test 90 @Test
  91 + public void testDeleteRelationConcurrently() throws ExecutionException, InterruptedException {
  92 + AssetId parentId = new AssetId(Uuids.timeBased());
  93 + AssetId childId = new AssetId(Uuids.timeBased());
  94 +
  95 + EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
  96 +
  97 + saveRelation(relationA);
  98 +
  99 + List<ListenableFuture<Boolean>> futures = new ArrayList<>();
  100 + for (int i = 0; i < 2; i++) {
  101 + futures.add(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA));
  102 + }
  103 + List<Boolean> results = Futures.allAsList(futures).get();
  104 + Assert.assertTrue(results.contains(true));
  105 + }
  106 +
  107 + @Test
88 public void testDeleteEntityRelations() throws ExecutionException, InterruptedException { 108 public void testDeleteEntityRelations() throws ExecutionException, InterruptedException {
89 AssetId parentId = new AssetId(Uuids.timeBased()); 109 AssetId parentId = new AssetId(Uuids.timeBased());
90 AssetId childId = new AssetId(Uuids.timeBased()); 110 AssetId childId = new AssetId(Uuids.timeBased());
@@ -55,6 +55,7 @@ import java.util.concurrent.ConcurrentHashMap; @@ -55,6 +55,7 @@ import java.util.concurrent.ConcurrentHashMap;
55 import java.util.concurrent.ConcurrentMap; 55 import java.util.concurrent.ConcurrentMap;
56 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.TimeUnit;
57 import java.util.concurrent.atomic.AtomicInteger; 57 import java.util.concurrent.atomic.AtomicInteger;
  58 +import java.util.function.BiConsumer;
58 59
59 /** 60 /**
60 * Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times 61 * Represents an MqttClientImpl connected to a single MQTT server. Will try to keep the connection going at all times
@@ -155,11 +156,14 @@ final class MqttClientImpl implements MqttClient { @@ -155,11 +156,14 @@ final class MqttClientImpl implements MqttClient {
155 if (callback != null) { 156 if (callback != null) {
156 callback.connectionLost(e); 157 callback.connectionLost(e);
157 } 158 }
  159 + pendingSubscriptions.forEach((id, mqttPendingSubscription) -> mqttPendingSubscription.onChannelClosed());
158 pendingSubscriptions.clear(); 160 pendingSubscriptions.clear();
159 serverSubscriptions.clear(); 161 serverSubscriptions.clear();
160 subscriptions.clear(); 162 subscriptions.clear();
  163 + pendingServerUnsubscribes.forEach((id, mqttPendingServerUnsubscribes) -> mqttPendingServerUnsubscribes.onChannelClosed());
161 pendingServerUnsubscribes.clear(); 164 pendingServerUnsubscribes.clear();
162 qos2PendingIncomingPublishes.clear(); 165 qos2PendingIncomingPublishes.clear();
  166 + pendingPublishes.forEach((id, mqttPendingPublish) -> mqttPendingPublish.onChannelClosed());
163 pendingPublishes.clear(); 167 pendingPublishes.clear();
164 pendingSubscribeTopics.clear(); 168 pendingSubscribeTopics.clear();
165 handlerToSubscribtion.clear(); 169 handlerToSubscribtion.clear();
@@ -366,19 +370,24 @@ final class MqttClientImpl implements MqttClient { @@ -366,19 +370,24 @@ final class MqttClientImpl implements MqttClient {
366 ChannelFuture channelFuture = this.sendAndFlushPacket(message); 370 ChannelFuture channelFuture = this.sendAndFlushPacket(message);
367 371
368 if (channelFuture != null) { 372 if (channelFuture != null) {
369 - pendingPublish.setSent(true);  
370 - if (channelFuture.cause() != null) {  
371 - future.setFailure(channelFuture.cause());  
372 - return future;  
373 - }  
374 - }  
375 - if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {  
376 - this.pendingPublishes.remove(pendingPublish.getMessageId());  
377 - pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0  
378 - } else if (pendingPublish.isSent()) {  
379 - pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket); 373 + channelFuture.addListener(result -> {
  374 + pendingPublish.setSent(true);
  375 + if (result.cause() != null) {
  376 + pendingPublishes.remove(pendingPublish.getMessageId());
  377 + future.setFailure(result.cause());
  378 + } else {
  379 + if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {
  380 + pendingPublishes.remove(pendingPublish.getMessageId());
  381 + pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
  382 + } else if (pendingPublish.isSent()) {
  383 + pendingPublish.startPublishRetransmissionTimer(eventLoop.next(), MqttClientImpl.this::sendAndFlushPacket);
  384 + } else {
  385 + pendingPublishes.remove(pendingPublish.getMessageId());
  386 + }
  387 + }
  388 + });
380 } else { 389 } else {
381 - this.pendingPublishes.remove(pendingPublish.getMessageId()); 390 + pendingPublishes.remove(pendingPublish.getMessageId());
382 } 391 }
383 return future; 392 return future;
384 } 393 }
@@ -24,7 +24,7 @@ import io.netty.util.concurrent.Promise; @@ -24,7 +24,7 @@ import io.netty.util.concurrent.Promise;
24 24
25 import java.util.function.Consumer; 25 import java.util.function.Consumer;
26 26
27 -final class MqttPendingPublish { 27 +final class MqttPendingPublish{
28 28
29 private final int messageId; 29 private final int messageId;
30 private final Promise<Void> future; 30 private final Promise<Void> future;
@@ -98,4 +98,9 @@ final class MqttPendingPublish { @@ -98,4 +98,9 @@ final class MqttPendingPublish {
98 void onPubcompReceived() { 98 void onPubcompReceived() {
99 this.pubrelRetransmissionHandler.stop(); 99 this.pubrelRetransmissionHandler.stop();
100 } 100 }
  101 +
  102 + void onChannelClosed(){
  103 + this.publishRetransmissionHandler.stop();
  104 + this.pubrelRetransmissionHandler.stop();
  105 + }
101 } 106 }
@@ -23,7 +23,7 @@ import java.util.HashSet; @@ -23,7 +23,7 @@ import java.util.HashSet;
23 import java.util.Set; 23 import java.util.Set;
24 import java.util.function.Consumer; 24 import java.util.function.Consumer;
25 25
26 -final class MqttPendingSubscription { 26 +final class MqttPendingSubscription{
27 27
28 private final Promise<Void> future; 28 private final Promise<Void> future;
29 private final String topic; 29 private final String topic;
@@ -99,4 +99,8 @@ final class MqttPendingSubscription { @@ -99,4 +99,8 @@ final class MqttPendingSubscription {
99 return once; 99 return once;
100 } 100 }
101 } 101 }
  102 +
  103 + void onChannelClosed(){
  104 + this.retransmissionHandler.stop();
  105 + }
102 } 106 }
@@ -21,7 +21,7 @@ import io.netty.util.concurrent.Promise; @@ -21,7 +21,7 @@ import io.netty.util.concurrent.Promise;
21 21
22 import java.util.function.Consumer; 22 import java.util.function.Consumer;
23 23
24 -final class MqttPendingUnsubscription { 24 +final class MqttPendingUnsubscription{
25 25
26 private final Promise<Void> future; 26 private final Promise<Void> future;
27 private final String topic; 27 private final String topic;
@@ -52,4 +52,8 @@ final class MqttPendingUnsubscription { @@ -52,4 +52,8 @@ final class MqttPendingUnsubscription {
52 void onUnsubackReceived(){ 52 void onUnsubackReceived(){
53 this.retransmissionHandler.stop(); 53 this.retransmissionHandler.stop();
54 } 54 }
  55 +
  56 + void onChannelClosed(){
  57 + this.retransmissionHandler.stop();
  58 + }
55 } 59 }
@@ -79,6 +79,9 @@ @@ -79,6 +79,9 @@
79 <mat-error *ngIf="domainInfo.get('name').hasError('pattern')"> 79 <mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
80 {{ 'admin.error-verification-url' | translate }} 80 {{ 'admin.error-verification-url' | translate }}
81 </mat-error> 81 </mat-error>
  82 + <mat-error *ngIf="domainInfo.get('name').hasError('maxlength')">
  83 + {{ 'admin.domain-name-max-length' | translate }}
  84 + </mat-error>
82 </mat-form-field> 85 </mat-form-field>
83 </div> 86 </div>
84 <mat-error *ngIf="domainInfo.hasError('unique')"> 87 <mat-error *ngIf="domainInfo.hasError('unique')">
@@ -246,6 +249,9 @@ @@ -246,6 +249,9 @@
246 <mat-error *ngIf="registration.get('clientId').hasError('required')"> 249 <mat-error *ngIf="registration.get('clientId').hasError('required')">
247 {{ 'admin.oauth2.client-id-required' | translate }} 250 {{ 'admin.oauth2.client-id-required' | translate }}
248 </mat-error> 251 </mat-error>
  252 + <mat-error *ngIf="registration.get('clientId').hasError('maxlength')">
  253 + {{ 'admin.oauth2.client-id-max-length' | translate }}
  254 + </mat-error>
249 </mat-form-field> 255 </mat-form-field>
250 256
251 <mat-form-field fxFlex class="mat-block"> 257 <mat-form-field fxFlex class="mat-block">
@@ -254,6 +260,9 @@ @@ -254,6 +260,9 @@
254 <mat-error *ngIf="registration.get('clientSecret').hasError('required')"> 260 <mat-error *ngIf="registration.get('clientSecret').hasError('required')">
255 {{ 'admin.oauth2.client-secret-required' | translate }} 261 {{ 'admin.oauth2.client-secret-required' | translate }}
256 </mat-error> 262 </mat-error>
  263 + <mat-error *ngIf="registration.get('clientSecret').hasError('maxlength')">
  264 + {{ 'admin.oauth2.client-secret-max-length' | translate }}
  265 + </mat-error>
257 </mat-form-field> 266 </mat-form-field>
258 </div> 267 </div>
259 268
@@ -426,17 +435,29 @@ @@ -426,17 +435,29 @@
426 *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')"> 435 *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
427 {{ 'admin.oauth2.email-attribute-key-required' | translate }} 436 {{ 'admin.oauth2.email-attribute-key-required' | translate }}
428 </mat-error> 437 </mat-error>
  438 + <mat-error
  439 + *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('maxlength')">
  440 + {{ 'admin.oauth2.email-attribute-key-max-length' | translate }}
  441 + </mat-error>
429 </mat-form-field> 442 </mat-form-field>
430 443
431 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> 444 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
432 <mat-form-field fxFlex class="mat-block"> 445 <mat-form-field fxFlex class="mat-block">
433 <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label> 446 <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
434 <input matInput formControlName="firstNameAttributeKey"> 447 <input matInput formControlName="firstNameAttributeKey">
  448 + <mat-error
  449 + *ngIf="registration.get('mapperConfig.basic.firstNameAttributeKey').hasError('maxlength')">
  450 + {{ 'admin.oauth2.first-name-attribute-key-max-length' | translate }}
  451 + </mat-error>
435 </mat-form-field> 452 </mat-form-field>
436 453
437 <mat-form-field fxFlex class="mat-block"> 454 <mat-form-field fxFlex class="mat-block">
438 <mat-label translate>admin.oauth2.last-name-attribute-key</mat-label> 455 <mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
439 <input matInput formControlName="lastNameAttributeKey"> 456 <input matInput formControlName="lastNameAttributeKey">
  457 + <mat-error
  458 + *ngIf="registration.get('mapperConfig.basic.lastNameAttributeKey').hasError('maxlength')">
  459 + {{ 'admin.oauth2.last-name-attribute-key-max-length' | translate }}
  460 + </mat-error>
440 </mat-form-field> 461 </mat-form-field>
441 </div> 462 </div>
442 463
@@ -460,18 +481,30 @@ @@ -460,18 +481,30 @@
460 *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')"> 481 *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
461 {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} 482 {{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
462 </mat-error> 483 </mat-error>
  484 + <mat-error
  485 + *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('maxlength')">
  486 + {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }}
  487 + </mat-error>
463 </mat-form-field> 488 </mat-form-field>
464 </div> 489 </div>
465 490
466 <mat-form-field fxFlex class="mat-block"> 491 <mat-form-field fxFlex class="mat-block">
467 <mat-label translate>admin.oauth2.customer-name-pattern</mat-label> 492 <mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
468 <input matInput formControlName="customerNamePattern"> 493 <input matInput formControlName="customerNamePattern">
  494 + <mat-error
  495 + *ngIf="registration.get('mapperConfig.basic.customerNamePattern').hasError('maxlength')">
  496 + {{ 'admin.oauth2.customer-name-pattern-max-length' | translate }}
  497 + </mat-error>
469 </mat-form-field> 498 </mat-form-field>
470 499
471 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> 500 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
472 <mat-form-field fxFlex class="mat-block"> 501 <mat-form-field fxFlex class="mat-block">
473 <mat-label translate>admin.oauth2.default-dashboard-name</mat-label> 502 <mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
474 <input matInput formControlName="defaultDashboardName"> 503 <input matInput formControlName="defaultDashboardName">
  504 + <mat-error
  505 + *ngIf="registration.get('mapperConfig.basic.defaultDashboardName').hasError('maxlength')">
  506 + {{ 'admin.oauth2.default-dashboard-name-max-length' | translate }}
  507 + </mat-error>
475 </mat-form-field> 508 </mat-form-field>
476 509
477 <mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row"> 510 <mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
@@ -493,18 +526,28 @@ @@ -493,18 +526,28 @@
493 *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')"> 526 *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
494 {{ 'admin.oauth2.url-pattern' | translate }} 527 {{ 'admin.oauth2.url-pattern' | translate }}
495 </mat-error> 528 </mat-error>
  529 + <mat-error
  530 + *ngIf="registration.get('mapperConfig.custom.url').hasError('maxlength')">
  531 + {{ 'admin.oauth2.url-max-length' | translate }}
  532 + </mat-error>
496 </mat-form-field> 533 </mat-form-field>
497 534
498 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px"> 535 <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
499 <mat-form-field fxFlex class="mat-block"> 536 <mat-form-field fxFlex class="mat-block">
500 <mat-label translate>common.username</mat-label> 537 <mat-label translate>common.username</mat-label>
501 <input matInput formControlName="username" autocomplete="new-username"> 538 <input matInput formControlName="username" autocomplete="new-username">
  539 + <mat-error *ngIf="registration.get('mapperConfig.custom.username').hasError('maxlength')">
  540 + {{ 'admin.oauth2.username-max-length' | translate }}
  541 + </mat-error>
502 </mat-form-field> 542 </mat-form-field>
503 543
504 <mat-form-field fxFlex class="mat-block"> 544 <mat-form-field fxFlex class="mat-block">
505 <mat-label translate>common.password</mat-label> 545 <mat-label translate>common.password</mat-label>
506 <input matInput type="password" formControlName="password" autocomplete="new-password"> 546 <input matInput type="password" formControlName="password" autocomplete="new-password">
507 <tb-toggle-password matSuffix></tb-toggle-password> 547 <tb-toggle-password matSuffix></tb-toggle-password>
  548 + <mat-error *ngIf="registration.get('mapperConfig.custom.password').hasError('maxlength')">
  549 + {{ 'admin.oauth2.password-max-length' | translate }}
  550 + </mat-error>
508 </mat-form-field> 551 </mat-form-field>
509 </div> 552 </div>
510 </section> 553 </section>
@@ -155,13 +155,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -155,13 +155,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
155 tenantNamePattern = {value: null, disabled: true}; 155 tenantNamePattern = {value: null, disabled: true};
156 } 156 }
157 const basicGroup = this.fb.group({ 157 const basicGroup = this.fb.group({
158 - emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],  
159 - firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],  
160 - lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], 158 + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email',
  159 + [Validators.required, Validators.maxLength(31)]],
  160 + firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : '',
  161 + Validators.maxLength(31)],
  162 + lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : '',
  163 + Validators.maxLength(31)],
161 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], 164 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
162 - tenantNamePattern: [tenantNamePattern, Validators.required],  
163 - customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],  
164 - defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null], 165 + tenantNamePattern: [tenantNamePattern, [Validators.required, Validators.maxLength(255)]],
  166 + customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null,
  167 + Validators.maxLength(255)],
  168 + defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null,
  169 + Validators.maxLength(255)],
165 alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] 170 alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
166 }); 171 });
167 172
@@ -178,9 +183,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -178,9 +183,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
178 183
179 private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup { 184 private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup {
180 return this.fb.group({ 185 return this.fb.group({
181 - url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]],  
182 - username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null],  
183 - password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null] 186 + url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null,
  187 + [Validators.required, Validators.pattern(this.URL_REGEXP), Validators.maxLength(255)]],
  188 + username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null, Validators.maxLength(255)],
  189 + password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)]
184 }); 190 });
185 } 191 }
186 192
@@ -266,7 +272,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -266,7 +272,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
266 private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): FormGroup { 272 private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): FormGroup {
267 return this.fb.group({ 273 return this.fb.group({
268 name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ 274 name: [domainInfo ? domainInfo.name : this.window.location.hostname, [
269 - Validators.required, 275 + Validators.required, Validators.maxLength(255),
270 Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], 276 Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]],
271 scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] 277 scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required]
272 }, {validators: this.uniqueDomainValidator}); 278 }, {validators: this.uniqueDomainValidator});
@@ -300,8 +306,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -300,8 +306,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
300 platforms: [registration?.platforms ? registration.platforms : []], 306 platforms: [registration?.platforms ? registration.platforms : []],
301 loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required], 307 loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required],
302 loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null], 308 loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null],
303 - clientId: [registration?.clientId ? registration.clientId : '', Validators.required],  
304 - clientSecret: [registration?.clientSecret ? registration.clientSecret : '', Validators.required], 309 + clientId: [registration?.clientId ? registration.clientId : '', [Validators.required, Validators.maxLength(255)]],
  310 + clientSecret: [registration?.clientSecret ? registration.clientSecret : '', [Validators.required, Validators.maxLength(2048)]],
305 accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '', 311 accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '',
306 [Validators.required, 312 [Validators.required,
307 Validators.pattern(this.URL_REGEXP)]], 313 Validators.pattern(this.URL_REGEXP)]],
@@ -143,8 +143,8 @@ @@ -143,8 +143,8 @@
143 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')"> 143 <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')">
144 {{ 'edge.edge-license-key-required' | translate }} 144 {{ 'edge.edge-license-key-required' | translate }}
145 </mat-error> 145 </mat-error>
146 - <mat-error *ngIf="entityForm.get('type').hasError('maxlength')">  
147 - {{ 'edge.type-max-length' | translate }} 146 + <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('maxlength')">
  147 + {{ 'edge.edge-license-key-max-length' | translate }}
148 </mat-error> 148 </mat-error>
149 </mat-form-field> 149 </mat-form-field>
150 </div> 150 </div>
@@ -156,6 +156,9 @@ @@ -156,6 +156,9 @@
156 <mat-error *ngIf="entityForm.get('cloudEndpoint').hasError('required')"> 156 <mat-error *ngIf="entityForm.get('cloudEndpoint').hasError('required')">
157 {{ 'edge.cloud-endpoint-required' | translate }} 157 {{ 'edge.cloud-endpoint-required' | translate }}
158 </mat-error> 158 </mat-error>
  159 + <mat-error *ngIf="entityForm.get('cloudEndpoint').hasError('maxlength')">
  160 + {{ 'edge.cloud-endpoint-max-length' | translate }}
  161 + </mat-error>
159 </mat-form-field> 162 </mat-form-field>
160 </div> 163 </div>
161 </fieldset> 164 </fieldset>
@@ -73,8 +73,8 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> { @@ -73,8 +73,8 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
73 name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]], 73 name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
74 type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]], 74 type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]],
75 label: [entity ? entity.label : '', Validators.maxLength(255)], 75 label: [entity ? entity.label : '', Validators.maxLength(255)],
76 - cloudEndpoint: [null, [Validators.required]],  
77 - edgeLicenseKey: ['', [Validators.required]], 76 + cloudEndpoint: [null, [Validators.required, Validators.maxLength(255)]],
  77 + edgeLicenseKey: ['', [Validators.required, Validators.maxLength(30)]],
78 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}), 78 routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}),
79 secret: this.fb.control({value: entity ? entity.secret : null, disabled: true}), 79 secret: this.fb.control({value: entity ? entity.secret : null, disabled: true}),
80 additionalInfo: this.fb.group( 80 additionalInfo: this.fb.group(
@@ -21,7 +21,7 @@ @@ -21,7 +21,7 @@
21 <mat-toolbar class="mat-elevation-z1 tb-edit-toolbar mat-hue-3" fxLayoutGap="16px" fxLayoutGap.lt-xl="8px"> 21 <mat-toolbar class="mat-elevation-z1 tb-edit-toolbar mat-hue-3" fxLayoutGap="16px" fxLayoutGap.lt-xl="8px">
22 <mat-form-field floatLabel="always" hideRequiredMarker class="tb-widget-title"> 22 <mat-form-field floatLabel="always" hideRequiredMarker class="tb-widget-title">
23 <mat-label></mat-label> 23 <mat-label></mat-label>
24 - <input [disabled]="isReadOnly" matInput required 24 + <input [disabled]="isReadOnly" matInput required maxlength="255"
25 [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true" 25 [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true"
26 placeholder="{{ 'widget.title' | translate }}"/> 26 placeholder="{{ 'widget.title' | translate }}"/>
27 </mat-form-field> 27 </mat-form-field>
@@ -158,6 +158,7 @@ @@ -158,6 +158,7 @@
158 "user-lockout-notification-email": "In case user account lockout, send notification to email", 158 "user-lockout-notification-email": "In case user account lockout, send notification to email",
159 "domain-name": "Domain name", 159 "domain-name": "Domain name",
160 "domain-name-unique": "Domain name and protocol need to unique.", 160 "domain-name-unique": "Domain name and protocol need to unique.",
  161 + "domain-name-max-length": "Domain name should be less than 256",
161 "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io", 162 "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
162 "oauth2": { 163 "oauth2": {
163 "access-token-uri": "Access token URI", 164 "access-token-uri": "Access token URI",
@@ -174,21 +175,28 @@ @@ -174,21 +175,28 @@
174 "client-authentication-method": "Client authentication method", 175 "client-authentication-method": "Client authentication method",
175 "client-id": "Client ID", 176 "client-id": "Client ID",
176 "client-id-required": "Client ID is required.", 177 "client-id-required": "Client ID is required.",
  178 + "client-id-max-length": "Client ID should be less than 256",
177 "client-secret": "Client secret", 179 "client-secret": "Client secret",
178 "client-secret-required": "Client secret is required.", 180 "client-secret-required": "Client secret is required.",
  181 + "client-secret-max-length": "Client secret should be less than 2049",
179 "custom-setting": "Custom settings", 182 "custom-setting": "Custom settings",
180 "customer-name-pattern": "Customer name pattern", 183 "customer-name-pattern": "Customer name pattern",
  184 + "customer-name-pattern-max-length": "Customer name pattern should be less than 256",
181 "default-dashboard-name": "Default dashboard name", 185 "default-dashboard-name": "Default dashboard name",
  186 + "default-dashboard-name-max-length": "Default dashboard name should be less than 256",
182 "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.", 187 "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
183 "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?", 188 "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
184 "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.", 189 "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
185 "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?", 190 "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
186 "email-attribute-key": "Email attribute key", 191 "email-attribute-key": "Email attribute key",
187 "email-attribute-key-required": "Email attribute key is required.", 192 "email-attribute-key-required": "Email attribute key is required.",
  193 + "email-attribute-key-max-length": "Email attribute key should be less than 32",
188 "first-name-attribute-key": "First name attribute key", 194 "first-name-attribute-key": "First name attribute key",
  195 + "first-name-attribute-key-max-length": "First name attribute key should be less than 32",
189 "general": "General", 196 "general": "General",
190 "jwk-set-uri": "JSON Web Key URI", 197 "jwk-set-uri": "JSON Web Key URI",
191 "last-name-attribute-key": "Last name attribute key", 198 "last-name-attribute-key": "Last name attribute key",
  199 + "last-name-attribute-key-max-length": "Last name attribute key should be less than 32",
192 "login-button-icon": "Login button icon", 200 "login-button-icon": "Login button icon",
193 "login-button-label": "Provider label", 201 "login-button-label": "Provider label",
194 "login-button-label-placeholder": "Login with $(Provider label)", 202 "login-button-label-placeholder": "Login with $(Provider label)",
@@ -197,6 +205,7 @@ @@ -197,6 +205,7 @@
197 "mapper": "Mapper", 205 "mapper": "Mapper",
198 "new-domain": "New domain", 206 "new-domain": "New domain",
199 "oauth2": "OAuth2", 207 "oauth2": "OAuth2",
  208 + "password-max-length": "Password should be less than 256",
200 "redirect-uri-template": "Redirect URI template", 209 "redirect-uri-template": "Redirect URI template",
201 "copy-redirect-uri": "Copy redirect URI", 210 "copy-redirect-uri": "Copy redirect URI",
202 "registration-id": "Registration ID", 211 "registration-id": "Registration ID",
@@ -206,14 +215,17 @@ @@ -206,14 +215,17 @@
206 "scope-required": "Scope is required.", 215 "scope-required": "Scope is required.",
207 "tenant-name-pattern": "Tenant name pattern", 216 "tenant-name-pattern": "Tenant name pattern",
208 "tenant-name-pattern-required": "Tenant name pattern is required.", 217 "tenant-name-pattern-required": "Tenant name pattern is required.",
  218 + "tenant-name-pattern-max-length": "Tenant name pattern ishould be less than 256",
209 "tenant-name-strategy": "Tenant name strategy", 219 "tenant-name-strategy": "Tenant name strategy",
210 "type": "Mapper type", 220 "type": "Mapper type",
211 "uri-pattern-error": "Invalid URI format.", 221 "uri-pattern-error": "Invalid URI format.",
212 "url": "URL", 222 "url": "URL",
213 "url-pattern": "Invalid URL format.", 223 "url-pattern": "Invalid URL format.",
214 "url-required": "URL is required.", 224 "url-required": "URL is required.",
  225 + "url-max-length": "URL should be less than 256",
215 "user-info-uri": "User info URI", 226 "user-info-uri": "User info URI",
216 "user-info-uri-required": "User info URI is required.", 227 "user-info-uri-required": "User info URI is required.",
  228 + "username-max-length": "User name should be less than 256",
217 "user-name-attribute-name": "User name attribute key", 229 "user-name-attribute-name": "User name attribute key",
218 "user-name-attribute-name-required": "User name attribute key is required", 230 "user-name-attribute-name-required": "User name attribute key is required",
219 "protocol": "Protocol", 231 "protocol": "Protocol",
@@ -1448,9 +1460,11 @@ @@ -1448,9 +1460,11 @@
1448 "name-required": "Name is required.", 1460 "name-required": "Name is required.",
1449 "edge-license-key": "Edge License Key", 1461 "edge-license-key": "Edge License Key",
1450 "edge-license-key-required": "Edge License Key is required.", 1462 "edge-license-key-required": "Edge License Key is required.",
  1463 + "edge-license-key-max-length": "Edge License Key should be less than 31",
1451 "edge-license-key-hint": "To obtain your license please navigate to the <a href='https://thingsboard.io/pricing/?active=thingsboard-edge' target='_blank'>pricing page</a> and select the best license option for your case.", 1464 "edge-license-key-hint": "To obtain your license please navigate to the <a href='https://thingsboard.io/pricing/?active=thingsboard-edge' target='_blank'>pricing page</a> and select the best license option for your case.",
1452 "cloud-endpoint": "Cloud Endpoint", 1465 "cloud-endpoint": "Cloud Endpoint",
1453 "cloud-endpoint-required": "Cloud Endpoint is required.", 1466 "cloud-endpoint-required": "Cloud Endpoint is required.",
  1467 + "cloud-endpoint-max-length": "Cloud Endpoint should be less than 256",
1454 "cloud-endpoint-hint": "Edge requires HTTP(s) access to Cloud (ThingsBoard CE/PE) to verify the license key. Please specify Cloud URL that Edge is able to connect to.", 1468 "cloud-endpoint-hint": "Edge requires HTTP(s) access to Cloud (ThingsBoard CE/PE) to verify the license key. Please specify Cloud URL that Edge is able to connect to.",
1455 "description": "Description", 1469 "description": "Description",
1456 "details": "Details", 1470 "details": "Details",