Commit 1affc60ace8015f38ab5d9c168dffded34245feb
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", |