Merge branch 'master' of github.com:thingsboard/thingsboard
This commit is contained in:
		
						commit
						1affc60ace
					
				@ -55,7 +55,7 @@
 | 
			
		||||
        "templateHtml": "<tb-timeseries-table-widget \n    [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
 | 
			
		||||
        "templateCss": "",
 | 
			
		||||
        "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}",
 | 
			
		||||
        "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}",
 | 
			
		||||
        "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}",
 | 
			
		||||
        "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}",
 | 
			
		||||
        "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\"}"
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.AdminSettingsId;
 | 
			
		||||
 | 
			
		||||
import com.fasterxml.jackson.databind.JsonNode;
 | 
			
		||||
import org.thingsboard.server.common.data.id.DeviceId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
@ApiModel
 | 
			
		||||
@ -29,6 +30,7 @@ public class AdminSettings extends BaseData<AdminSettingsId> {
 | 
			
		||||
    private static final long serialVersionUID = -7670322981725511892L;
 | 
			
		||||
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "key")
 | 
			
		||||
    private String key;
 | 
			
		||||
    private transient JsonNode jsonValue;
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
@ -68,10 +68,13 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true)
 | 
			
		||||
    private String fileName;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "contentType")
 | 
			
		||||
    @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true)
 | 
			
		||||
    private String contentType;
 | 
			
		||||
    @ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", readOnly = true)
 | 
			
		||||
    private ChecksumAlgorithm checksumAlgorithm;
 | 
			
		||||
    @Length(fieldName = "checksum", max = 1020)
 | 
			
		||||
    @ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", readOnly = true)
 | 
			
		||||
    private String checksum;
 | 
			
		||||
    @ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", readOnly = true)
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,8 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
 | 
			
		||||
    private String title;
 | 
			
		||||
    @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true)
 | 
			
		||||
    private ResourceType resourceType;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "resourceKey")
 | 
			
		||||
    @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true)
 | 
			
		||||
    private String resourceKey;
 | 
			
		||||
    @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 {
 | 
			
		||||
    @ApiModelProperty(position = 3, value = "Title of the tenant", example = "Company A")
 | 
			
		||||
    private String title;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "region")
 | 
			
		||||
    @ApiModelProperty(position = 5, value = "Geo region of the tenant", example = "North America")
 | 
			
		||||
    private String region;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.RuleChainId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
@ApiModel
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@ -41,15 +42,26 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
 | 
			
		||||
    private TenantId tenantId;
 | 
			
		||||
    private CustomerId customerId;
 | 
			
		||||
    private RuleChainId rootRuleChainId;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "name")
 | 
			
		||||
    private String name;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "type")
 | 
			
		||||
    private String type;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "label")
 | 
			
		||||
    private String label;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "routingKey")
 | 
			
		||||
    private String routingKey;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "secret")
 | 
			
		||||
    private String secret;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "edgeLicenseKey", max = 30)
 | 
			
		||||
    private String edgeLicenseKey;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "cloudEndpoint")
 | 
			
		||||
    private String cloudEndpoint;
 | 
			
		||||
 | 
			
		||||
    public Edge() {
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
 | 
			
		||||
@Builder(toBuilder = true)
 | 
			
		||||
@EqualsAndHashCode
 | 
			
		||||
@ -28,21 +29,27 @@ import lombok.ToString;
 | 
			
		||||
@ToString
 | 
			
		||||
@ApiModel
 | 
			
		||||
public class OAuth2BasicMapperConfig {
 | 
			
		||||
    @Length(fieldName = "emailAttributeKey", max = 31)
 | 
			
		||||
    @ApiModelProperty(value = "Email attribute key of OAuth2 principal attributes. " +
 | 
			
		||||
            "Must be specified for BASIC mapper type and cannot be specified for GITHUB type")
 | 
			
		||||
    private final String emailAttributeKey;
 | 
			
		||||
    @Length(fieldName = "firstNameAttributeKey", max = 31)
 | 
			
		||||
    @ApiModelProperty(value = "First name attribute key")
 | 
			
		||||
    private final String firstNameAttributeKey;
 | 
			
		||||
    @Length(fieldName = "lastNameAttributeKey", max = 31)
 | 
			
		||||
    @ApiModelProperty(value = "Last name attribute key")
 | 
			
		||||
    private final String lastNameAttributeKey;
 | 
			
		||||
    @ApiModelProperty(value = "Tenant naming strategy. For DOMAIN type, domain for tenant name will be taken from the email (substring before '@')", required = true)
 | 
			
		||||
    private final TenantNameStrategyType tenantNameStrategy;
 | 
			
		||||
    @Length(fieldName = "tenantNamePattern")
 | 
			
		||||
    @ApiModelProperty(value = "Tenant name pattern for CUSTOM naming strategy. " +
 | 
			
		||||
            "OAuth2 attributes in the pattern can be used by enclosing attribute key in '%{' and '}'", example = "%{email}")
 | 
			
		||||
    private final String tenantNamePattern;
 | 
			
		||||
    @Length(fieldName = "customerNamePattern")
 | 
			
		||||
    @ApiModelProperty(value = "Customer name pattern. When creating a user on the first OAuth2 log in, if specified, " +
 | 
			
		||||
            "customer name will be used to create or find existing customer in the platform and assign customerId to the user")
 | 
			
		||||
    private final String customerNamePattern;
 | 
			
		||||
    @Length(fieldName = "defaultDashboardName")
 | 
			
		||||
    @ApiModelProperty(value = "Name of the tenant's dashboard to set as default dashboard for newly created user")
 | 
			
		||||
    private final String defaultDashboardName;
 | 
			
		||||
    @ApiModelProperty(value = "Whether default dashboard should be open in full screen")
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,9 @@ import lombok.ToString;
 | 
			
		||||
import org.thingsboard.server.common.data.HasName;
 | 
			
		||||
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
 | 
			
		||||
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@ -34,30 +36,41 @@ import java.util.List;
 | 
			
		||||
@ApiModel
 | 
			
		||||
public class OAuth2ClientRegistrationTemplate extends SearchTextBasedWithAdditionalInfo<OAuth2ClientRegistrationTemplateId> implements HasName {
 | 
			
		||||
 | 
			
		||||
    @Length(fieldName = "providerId")
 | 
			
		||||
    @ApiModelProperty(value = "OAuth2 provider identifier (e.g. its name)", required = true)
 | 
			
		||||
    private String providerId;
 | 
			
		||||
    @Valid
 | 
			
		||||
    @ApiModelProperty(value = "Default config for mapping OAuth2 log in response to platform entities")
 | 
			
		||||
    private OAuth2MapperConfig mapperConfig;
 | 
			
		||||
    @Length(fieldName = "authorizationUri")
 | 
			
		||||
    @ApiModelProperty(value = "Default authorization URI of the OAuth2 provider")
 | 
			
		||||
    private String authorizationUri;
 | 
			
		||||
    @Length(fieldName = "accessTokenUri")
 | 
			
		||||
    @ApiModelProperty(value = "Default access token URI of the OAuth2 provider")
 | 
			
		||||
    private String accessTokenUri;
 | 
			
		||||
    @ApiModelProperty(value = "Default OAuth scopes that will be requested from OAuth2 platform")
 | 
			
		||||
    private List<String> scope;
 | 
			
		||||
    @Length(fieldName = "userInfoUri")
 | 
			
		||||
    @ApiModelProperty(value = "Default user info URI of the OAuth2 provider")
 | 
			
		||||
    private String userInfoUri;
 | 
			
		||||
    @Length(fieldName = "userNameAttributeName")
 | 
			
		||||
    @ApiModelProperty(value = "Default name of the username attribute in OAuth2 provider log in response")
 | 
			
		||||
    private String userNameAttributeName;
 | 
			
		||||
    @Length(fieldName = "jwkSetUri")
 | 
			
		||||
    @ApiModelProperty(value = "Default JSON Web Key URI of the OAuth2 provider")
 | 
			
		||||
    private String jwkSetUri;
 | 
			
		||||
    @Length(fieldName = "clientAuthenticationMethod")
 | 
			
		||||
    @ApiModelProperty(value = "Default client authentication method to use: 'BASIC' or 'POST'")
 | 
			
		||||
    private String clientAuthenticationMethod;
 | 
			
		||||
    @ApiModelProperty(value = "Comment for OAuth2 provider")
 | 
			
		||||
    private String comment;
 | 
			
		||||
    @Length(fieldName = "loginButtonIcon")
 | 
			
		||||
    @ApiModelProperty(value = "Default log in button icon for OAuth2 provider")
 | 
			
		||||
    private String loginButtonIcon;
 | 
			
		||||
    @Length(fieldName = "loginButtonLabel")
 | 
			
		||||
    @ApiModelProperty(value = "Default OAuth2 provider label")
 | 
			
		||||
    private String loginButtonLabel;
 | 
			
		||||
    @Length(fieldName = "helpLink")
 | 
			
		||||
    @ApiModelProperty(value = "Help link for OAuth2 provider")
 | 
			
		||||
    private String helpLink;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -15,15 +15,22 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.thingsboard.server.common.data.oauth2;
 | 
			
		||||
 | 
			
		||||
import lombok.*;
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
 | 
			
		||||
@Builder(toBuilder = true)
 | 
			
		||||
@EqualsAndHashCode
 | 
			
		||||
@Data
 | 
			
		||||
@ToString(exclude = {"password"})
 | 
			
		||||
public class OAuth2CustomMapperConfig {
 | 
			
		||||
    @Length(fieldName = "url")
 | 
			
		||||
    private final String url;
 | 
			
		||||
    @Length(fieldName = "username")
 | 
			
		||||
    private final String username;
 | 
			
		||||
    @Length(fieldName = "password")
 | 
			
		||||
    private final String password;
 | 
			
		||||
    private final boolean sendToken;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,8 @@ import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import lombok.ToString;
 | 
			
		||||
 | 
			
		||||
import javax.validation.Valid;
 | 
			
		||||
 | 
			
		||||
@Builder(toBuilder = true)
 | 
			
		||||
@EqualsAndHashCode
 | 
			
		||||
@Data
 | 
			
		||||
@ -32,8 +34,10 @@ public class OAuth2MapperConfig {
 | 
			
		||||
    private boolean activateUser;
 | 
			
		||||
    @ApiModelProperty(value = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", required = true)
 | 
			
		||||
    private MapperType type;
 | 
			
		||||
    @Valid
 | 
			
		||||
    @ApiModelProperty(value = "Mapper config for BASIC and GITHUB mapper types")
 | 
			
		||||
    private OAuth2BasicMapperConfig basic;
 | 
			
		||||
    @Valid
 | 
			
		||||
    @ApiModelProperty(value = "Mapper config for CUSTOM mapper type")
 | 
			
		||||
    private OAuth2CustomMapperConfig custom;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import io.swagger.annotations.ApiModelProperty;
 | 
			
		||||
import lombok.*;
 | 
			
		||||
import org.thingsboard.server.common.data.SearchTextBased;
 | 
			
		||||
import org.thingsboard.server.common.data.id.ComponentDescriptorId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Andrew Shvayka
 | 
			
		||||
@ -35,12 +36,14 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId>
 | 
			
		||||
    @Getter @Setter private ComponentType type;
 | 
			
		||||
    @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")
 | 
			
		||||
    @Getter @Setter private ComponentScope scope;
 | 
			
		||||
    @Length(fieldName = "name")
 | 
			
		||||
    @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node")
 | 
			
		||||
    @Getter @Setter private String name;
 | 
			
		||||
    @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode")
 | 
			
		||||
    @Getter @Setter private String clazz;
 | 
			
		||||
    @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true)
 | 
			
		||||
    @Getter @Setter private transient JsonNode configurationDescriptor;
 | 
			
		||||
    @Length(fieldName = "actions")
 | 
			
		||||
    @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true)
 | 
			
		||||
    @Getter @Setter private String actions;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.BaseData;
 | 
			
		||||
import org.thingsboard.server.common.data.HasTenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@ -31,12 +32,15 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasTenantI
 | 
			
		||||
    @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true)
 | 
			
		||||
    private TenantId tenantId;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "bundleAlias")
 | 
			
		||||
    @ApiModelProperty(position = 4, value = "Reference to widget bundle", readOnly = true)
 | 
			
		||||
    private String bundleAlias;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "alias")
 | 
			
		||||
    @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", readOnly = true)
 | 
			
		||||
    private String alias;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "name")
 | 
			
		||||
    @ApiModelProperty(position = 6, value = "Widget name used in search and UI", readOnly = true)
 | 
			
		||||
    private String name;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -19,15 +19,18 @@ import com.fasterxml.jackson.annotation.JsonPropertyOrder;
 | 
			
		||||
import io.swagger.annotations.ApiModelProperty;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.thingsboard.server.common.data.id.WidgetTypeId;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.Length;
 | 
			
		||||
import org.thingsboard.server.common.data.validation.NoXss;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@JsonPropertyOrder({ "alias", "name", "image", "description", "descriptor" })
 | 
			
		||||
public class WidgetTypeDetails extends WidgetType {
 | 
			
		||||
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", readOnly = true)
 | 
			
		||||
    private String image;
 | 
			
		||||
    @NoXss
 | 
			
		||||
    @Length(fieldName = "description")
 | 
			
		||||
    @ApiModelProperty(position = 9, value = "Description of the widget", readOnly = true)
 | 
			
		||||
    private String description;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -34,10 +34,9 @@ public class CoapEfentoCallback implements TransportServiceCallback<Void> {
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void onSuccess(Void msg) {
 | 
			
		||||
        //We respond only to confirmed requests in order to reduce battery consumption for Efento devices.
 | 
			
		||||
        if (isConRequest()) {
 | 
			
		||||
            Response response = new Response(onSuccessResponse);
 | 
			
		||||
            response.setAcknowledged(true);
 | 
			
		||||
            exchange.respond(response);
 | 
			
		||||
            exchange.respond(new Response(onSuccessResponse));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -99,10 +99,9 @@ public class CoapEfentoTransportResource extends AbstractCoapTransportResource {
 | 
			
		||||
                break;
 | 
			
		||||
            case DEVICE_INFO:
 | 
			
		||||
            case CONFIGURATION:
 | 
			
		||||
                Response response = new Response(CoAP.ResponseCode.CREATED);
 | 
			
		||||
                //We respond only to confirmed requests in order to reduce battery consumption for Efento devices.
 | 
			
		||||
                if (exchange.advanced().getRequest().isConfirmable()) {
 | 
			
		||||
                    response.setAcknowledged(true);
 | 
			
		||||
                    exchange.respond(response);
 | 
			
		||||
                    exchange.respond(new Response(CoAP.ResponseCode.CREATED));
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
 | 
			
		||||
@ -157,7 +157,11 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
 | 
			
		||||
    private boolean deleteRelationIfExists(RelationCompositeKey key) {
 | 
			
		||||
        boolean relationExistsBeforeDelete = relationRepository.existsById(key);
 | 
			
		||||
        if (relationExistsBeforeDelete) {
 | 
			
		||||
            relationRepository.deleteById(key);
 | 
			
		||||
            try {
 | 
			
		||||
                relationRepository.deleteById(key);
 | 
			
		||||
            } catch (ConcurrencyFailureException e) {
 | 
			
		||||
                log.debug("[{}] Concurrency exception while deleting relation", key, e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return relationExistsBeforeDelete;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,8 @@
 | 
			
		||||
package org.thingsboard.server.dao.service;
 | 
			
		||||
 | 
			
		||||
import com.datastax.oss.driver.api.core.uuid.Uuids;
 | 
			
		||||
import com.google.common.util.concurrent.Futures;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
import org.junit.After;
 | 
			
		||||
import org.junit.Assert;
 | 
			
		||||
import org.junit.Before;
 | 
			
		||||
@ -31,6 +33,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 | 
			
		||||
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
 | 
			
		||||
import org.thingsboard.server.dao.exception.DataValidationException;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
@ -84,6 +87,23 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
 | 
			
		||||
        Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDeleteRelationConcurrently() throws ExecutionException, InterruptedException {
 | 
			
		||||
        AssetId parentId = new AssetId(Uuids.timeBased());
 | 
			
		||||
        AssetId childId = new AssetId(Uuids.timeBased());
 | 
			
		||||
 | 
			
		||||
        EntityRelation relationA = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
 | 
			
		||||
 | 
			
		||||
        saveRelation(relationA);
 | 
			
		||||
 | 
			
		||||
        List<ListenableFuture<Boolean>> futures = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < 2; i++) {
 | 
			
		||||
            futures.add(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA));
 | 
			
		||||
        }
 | 
			
		||||
        List<Boolean> results = Futures.allAsList(futures).get();
 | 
			
		||||
        Assert.assertTrue(results.contains(true));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testDeleteEntityRelations() throws ExecutionException, InterruptedException {
 | 
			
		||||
        AssetId parentId = new AssetId(Uuids.timeBased());
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@ import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.ConcurrentMap;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
			
		||||
import java.util.function.BiConsumer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 {
 | 
			
		||||
                    if (callback != null) {
 | 
			
		||||
                        callback.connectionLost(e);
 | 
			
		||||
                    }
 | 
			
		||||
                    pendingSubscriptions.forEach((id, mqttPendingSubscription) -> mqttPendingSubscription.onChannelClosed());
 | 
			
		||||
                    pendingSubscriptions.clear();
 | 
			
		||||
                    serverSubscriptions.clear();
 | 
			
		||||
                    subscriptions.clear();
 | 
			
		||||
                    pendingServerUnsubscribes.forEach((id, mqttPendingServerUnsubscribes) -> mqttPendingServerUnsubscribes.onChannelClosed());
 | 
			
		||||
                    pendingServerUnsubscribes.clear();
 | 
			
		||||
                    qos2PendingIncomingPublishes.clear();
 | 
			
		||||
                    pendingPublishes.forEach((id, mqttPendingPublish) -> mqttPendingPublish.onChannelClosed());
 | 
			
		||||
                    pendingPublishes.clear();
 | 
			
		||||
                    pendingSubscribeTopics.clear();
 | 
			
		||||
                    handlerToSubscribtion.clear();
 | 
			
		||||
@ -366,19 +370,24 @@ final class MqttClientImpl implements MqttClient {
 | 
			
		||||
        ChannelFuture channelFuture = this.sendAndFlushPacket(message);
 | 
			
		||||
 | 
			
		||||
        if (channelFuture != null) {
 | 
			
		||||
            pendingPublish.setSent(true);
 | 
			
		||||
            if (channelFuture.cause() != null) {
 | 
			
		||||
                future.setFailure(channelFuture.cause());
 | 
			
		||||
                return future;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {
 | 
			
		||||
            this.pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
            pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
 | 
			
		||||
        } else if (pendingPublish.isSent()) {
 | 
			
		||||
            pendingPublish.startPublishRetransmissionTimer(this.eventLoop.next(), this::sendAndFlushPacket);
 | 
			
		||||
            channelFuture.addListener(result -> {
 | 
			
		||||
                pendingPublish.setSent(true);
 | 
			
		||||
                if (result.cause() != null) {
 | 
			
		||||
                    pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
                    future.setFailure(result.cause());
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (pendingPublish.isSent() && pendingPublish.getQos() == MqttQoS.AT_MOST_ONCE) {
 | 
			
		||||
                        pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
                        pendingPublish.getFuture().setSuccess(null); //We don't get an ACK for QOS 0
 | 
			
		||||
                    } else if (pendingPublish.isSent()) {
 | 
			
		||||
                        pendingPublish.startPublishRetransmissionTimer(eventLoop.next(), MqttClientImpl.this::sendAndFlushPacket);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
        } else {
 | 
			
		||||
            this.pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
            pendingPublishes.remove(pendingPublish.getMessageId());
 | 
			
		||||
        }
 | 
			
		||||
        return future;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -24,7 +24,7 @@ import io.netty.util.concurrent.Promise;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
final class MqttPendingPublish {
 | 
			
		||||
final class MqttPendingPublish{
 | 
			
		||||
 | 
			
		||||
    private final int messageId;
 | 
			
		||||
    private final Promise<Void> future;
 | 
			
		||||
@ -98,4 +98,9 @@ final class MqttPendingPublish {
 | 
			
		||||
    void onPubcompReceived() {
 | 
			
		||||
        this.pubrelRetransmissionHandler.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onChannelClosed(){
 | 
			
		||||
        this.publishRetransmissionHandler.stop();
 | 
			
		||||
        this.pubrelRetransmissionHandler.stop();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,7 @@ import java.util.HashSet;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
final class MqttPendingSubscription {
 | 
			
		||||
final class MqttPendingSubscription{
 | 
			
		||||
 | 
			
		||||
    private final Promise<Void> future;
 | 
			
		||||
    private final String topic;
 | 
			
		||||
@ -99,4 +99,8 @@ final class MqttPendingSubscription {
 | 
			
		||||
            return once;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onChannelClosed(){
 | 
			
		||||
        this.retransmissionHandler.stop();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ import io.netty.util.concurrent.Promise;
 | 
			
		||||
 | 
			
		||||
import java.util.function.Consumer;
 | 
			
		||||
 | 
			
		||||
final class MqttPendingUnsubscription {
 | 
			
		||||
final class MqttPendingUnsubscription{
 | 
			
		||||
 | 
			
		||||
    private final Promise<Void> future;
 | 
			
		||||
    private final String topic;
 | 
			
		||||
@ -52,4 +52,8 @@ final class MqttPendingUnsubscription {
 | 
			
		||||
    void onUnsubackReceived(){
 | 
			
		||||
        this.retransmissionHandler.stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void onChannelClosed(){
 | 
			
		||||
        this.retransmissionHandler.stop();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,9 @@
 | 
			
		||||
                                          <mat-error *ngIf="domainInfo.get('name').hasError('pattern')">
 | 
			
		||||
                                            {{ 'admin.error-verification-url' | translate }}
 | 
			
		||||
                                          </mat-error>
 | 
			
		||||
                                          <mat-error *ngIf="domainInfo.get('name').hasError('maxlength')">
 | 
			
		||||
                                            {{ 'admin.domain-name-max-length' | translate }}
 | 
			
		||||
                                          </mat-error>
 | 
			
		||||
                                        </mat-form-field>
 | 
			
		||||
                                      </div>
 | 
			
		||||
                                      <mat-error *ngIf="domainInfo.hasError('unique')">
 | 
			
		||||
@ -246,6 +249,9 @@
 | 
			
		||||
                                      <mat-error *ngIf="registration.get('clientId').hasError('required')">
 | 
			
		||||
                                        {{ 'admin.oauth2.client-id-required' | translate }}
 | 
			
		||||
                                      </mat-error>
 | 
			
		||||
                                      <mat-error *ngIf="registration.get('clientId').hasError('maxlength')">
 | 
			
		||||
                                        {{ 'admin.oauth2.client-id-max-length' | translate }}
 | 
			
		||||
                                      </mat-error>
 | 
			
		||||
                                    </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                    <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
@ -254,6 +260,9 @@
 | 
			
		||||
                                      <mat-error *ngIf="registration.get('clientSecret').hasError('required')">
 | 
			
		||||
                                        {{ 'admin.oauth2.client-secret-required' | translate }}
 | 
			
		||||
                                      </mat-error>
 | 
			
		||||
                                      <mat-error *ngIf="registration.get('clientSecret').hasError('maxlength')">
 | 
			
		||||
                                        {{ 'admin.oauth2.client-secret-max-length' | translate }}
 | 
			
		||||
                                      </mat-error>
 | 
			
		||||
                                    </mat-form-field>
 | 
			
		||||
                                  </div>
 | 
			
		||||
 | 
			
		||||
@ -426,17 +435,29 @@
 | 
			
		||||
                                                  *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('required')">
 | 
			
		||||
                                                  {{ 'admin.oauth2.email-attribute-key-required' | translate }}
 | 
			
		||||
                                                </mat-error>
 | 
			
		||||
                                                <mat-error
 | 
			
		||||
                                                  *ngIf="registration.get('mapperConfig.basic.emailAttributeKey').hasError('maxlength')">
 | 
			
		||||
                                                  {{ 'admin.oauth2.email-attribute-key-max-length' | translate }}
 | 
			
		||||
                                                </mat-error>
 | 
			
		||||
                                              </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                              <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
 | 
			
		||||
                                                <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                  <mat-label translate>admin.oauth2.first-name-attribute-key</mat-label>
 | 
			
		||||
                                                  <input matInput formControlName="firstNameAttributeKey">
 | 
			
		||||
                                                  <mat-error
 | 
			
		||||
                                                    *ngIf="registration.get('mapperConfig.basic.firstNameAttributeKey').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.first-name-attribute-key-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                                <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                  <mat-label translate>admin.oauth2.last-name-attribute-key</mat-label>
 | 
			
		||||
                                                  <input matInput formControlName="lastNameAttributeKey">
 | 
			
		||||
                                                  <mat-error
 | 
			
		||||
                                                    *ngIf="registration.get('mapperConfig.basic.lastNameAttributeKey').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.last-name-attribute-key-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
                                              </div>
 | 
			
		||||
 | 
			
		||||
@ -460,18 +481,30 @@
 | 
			
		||||
                                                    *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('required')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.tenant-name-pattern-required' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                  <mat-error
 | 
			
		||||
                                                    *ngIf="registration.get('mapperConfig.basic.tenantNamePattern').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
                                              </div>
 | 
			
		||||
 | 
			
		||||
                                              <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                <mat-label translate>admin.oauth2.customer-name-pattern</mat-label>
 | 
			
		||||
                                                <input matInput formControlName="customerNamePattern">
 | 
			
		||||
                                                <mat-error
 | 
			
		||||
                                                  *ngIf="registration.get('mapperConfig.basic.customerNamePattern').hasError('maxlength')">
 | 
			
		||||
                                                  {{ 'admin.oauth2.customer-name-pattern-max-length' | translate }}
 | 
			
		||||
                                                </mat-error>
 | 
			
		||||
                                              </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                              <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
 | 
			
		||||
                                                <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                  <mat-label translate>admin.oauth2.default-dashboard-name</mat-label>
 | 
			
		||||
                                                  <input matInput formControlName="defaultDashboardName">
 | 
			
		||||
                                                  <mat-error
 | 
			
		||||
                                                    *ngIf="registration.get('mapperConfig.basic.defaultDashboardName').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.default-dashboard-name-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                                <mat-checkbox fxFlex formControlName="alwaysFullScreen" class="checkbox-row">
 | 
			
		||||
@ -493,18 +526,28 @@
 | 
			
		||||
                                                  *ngIf="registration.get('mapperConfig.custom.url').hasError('pattern')">
 | 
			
		||||
                                                  {{ 'admin.oauth2.url-pattern' | translate }}
 | 
			
		||||
                                                </mat-error>
 | 
			
		||||
                                                <mat-error
 | 
			
		||||
                                                  *ngIf="registration.get('mapperConfig.custom.url').hasError('maxlength')">
 | 
			
		||||
                                                  {{ 'admin.oauth2.url-max-length' | translate }}
 | 
			
		||||
                                                </mat-error>
 | 
			
		||||
                                              </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                              <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="8px">
 | 
			
		||||
                                                <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                  <mat-label translate>common.username</mat-label>
 | 
			
		||||
                                                  <input matInput formControlName="username" autocomplete="new-username">
 | 
			
		||||
                                                  <mat-error *ngIf="registration.get('mapperConfig.custom.username').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.username-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
 | 
			
		||||
                                                <mat-form-field fxFlex class="mat-block">
 | 
			
		||||
                                                  <mat-label translate>common.password</mat-label>
 | 
			
		||||
                                                  <input matInput type="password" formControlName="password" autocomplete="new-password">
 | 
			
		||||
                                                  <tb-toggle-password matSuffix></tb-toggle-password>
 | 
			
		||||
                                                  <mat-error *ngIf="registration.get('mapperConfig.custom.password').hasError('maxlength')">
 | 
			
		||||
                                                    {{ 'admin.oauth2.password-max-length' | translate }}
 | 
			
		||||
                                                  </mat-error>
 | 
			
		||||
                                                </mat-form-field>
 | 
			
		||||
                                              </div>
 | 
			
		||||
                                            </section>
 | 
			
		||||
 | 
			
		||||
@ -155,13 +155,18 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
 | 
			
		||||
      tenantNamePattern = {value: null, disabled: true};
 | 
			
		||||
    }
 | 
			
		||||
    const basicGroup = this.fb.group({
 | 
			
		||||
      emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
 | 
			
		||||
      firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
 | 
			
		||||
      lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
 | 
			
		||||
      emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email',
 | 
			
		||||
        [Validators.required, Validators.maxLength(31)]],
 | 
			
		||||
      firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : '',
 | 
			
		||||
        Validators.maxLength(31)],
 | 
			
		||||
      lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : '',
 | 
			
		||||
        Validators.maxLength(31)],
 | 
			
		||||
      tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
 | 
			
		||||
      tenantNamePattern: [tenantNamePattern, Validators.required],
 | 
			
		||||
      customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null],
 | 
			
		||||
      defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null],
 | 
			
		||||
      tenantNamePattern: [tenantNamePattern, [Validators.required, Validators.maxLength(255)]],
 | 
			
		||||
      customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null,
 | 
			
		||||
        Validators.maxLength(255)],
 | 
			
		||||
      defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null,
 | 
			
		||||
        Validators.maxLength(255)],
 | 
			
		||||
      alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -178,9 +183,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
 | 
			
		||||
 | 
			
		||||
  private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): FormGroup {
 | 
			
		||||
    return this.fb.group({
 | 
			
		||||
      url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, [Validators.required, Validators.pattern(this.URL_REGEXP)]],
 | 
			
		||||
      username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null],
 | 
			
		||||
      password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null]
 | 
			
		||||
      url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null,
 | 
			
		||||
        [Validators.required, Validators.pattern(this.URL_REGEXP), Validators.maxLength(255)]],
 | 
			
		||||
      username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null, Validators.maxLength(255)],
 | 
			
		||||
      password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)]
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -266,7 +272,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
 | 
			
		||||
  private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): FormGroup {
 | 
			
		||||
    return this.fb.group({
 | 
			
		||||
      name: [domainInfo ? domainInfo.name : this.window.location.hostname, [
 | 
			
		||||
        Validators.required,
 | 
			
		||||
        Validators.required, Validators.maxLength(255),
 | 
			
		||||
        Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]],
 | 
			
		||||
      scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required]
 | 
			
		||||
    }, {validators: this.uniqueDomainValidator});
 | 
			
		||||
@ -300,8 +306,8 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
 | 
			
		||||
      platforms: [registration?.platforms ? registration.platforms : []],
 | 
			
		||||
      loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required],
 | 
			
		||||
      loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null],
 | 
			
		||||
      clientId: [registration?.clientId ? registration.clientId : '', Validators.required],
 | 
			
		||||
      clientSecret: [registration?.clientSecret ? registration.clientSecret : '', Validators.required],
 | 
			
		||||
      clientId: [registration?.clientId ? registration.clientId : '', [Validators.required, Validators.maxLength(255)]],
 | 
			
		||||
      clientSecret: [registration?.clientSecret ? registration.clientSecret : '', [Validators.required, Validators.maxLength(2048)]],
 | 
			
		||||
      accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '',
 | 
			
		||||
        [Validators.required,
 | 
			
		||||
          Validators.pattern(this.URL_REGEXP)]],
 | 
			
		||||
 | 
			
		||||
@ -143,8 +143,8 @@
 | 
			
		||||
          <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('required')">
 | 
			
		||||
            {{ 'edge.edge-license-key-required' | translate }}
 | 
			
		||||
          </mat-error>
 | 
			
		||||
          <mat-error *ngIf="entityForm.get('type').hasError('maxlength')">
 | 
			
		||||
            {{ 'edge.type-max-length' | translate }}
 | 
			
		||||
          <mat-error *ngIf="entityForm.get('edgeLicenseKey').hasError('maxlength')">
 | 
			
		||||
            {{ 'edge.edge-license-key-max-length' | translate }}
 | 
			
		||||
          </mat-error>
 | 
			
		||||
        </mat-form-field>
 | 
			
		||||
      </div>
 | 
			
		||||
@ -156,6 +156,9 @@
 | 
			
		||||
          <mat-error *ngIf="entityForm.get('cloudEndpoint').hasError('required')">
 | 
			
		||||
            {{ 'edge.cloud-endpoint-required' | translate }}
 | 
			
		||||
          </mat-error>
 | 
			
		||||
          <mat-error *ngIf="entityForm.get('cloudEndpoint').hasError('maxlength')">
 | 
			
		||||
            {{ 'edge.cloud-endpoint-max-length' | translate }}
 | 
			
		||||
          </mat-error>
 | 
			
		||||
        </mat-form-field>
 | 
			
		||||
      </div>
 | 
			
		||||
    </fieldset>
 | 
			
		||||
 | 
			
		||||
@ -73,8 +73,8 @@ export class EdgeComponent extends EntityComponent<EdgeInfo> {
 | 
			
		||||
        name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]],
 | 
			
		||||
        type: [entity?.type ? entity.type : 'default', [Validators.required, Validators.maxLength(255)]],
 | 
			
		||||
        label: [entity ? entity.label : '', Validators.maxLength(255)],
 | 
			
		||||
        cloudEndpoint: [null, [Validators.required]],
 | 
			
		||||
        edgeLicenseKey: ['', [Validators.required]],
 | 
			
		||||
        cloudEndpoint: [null, [Validators.required, Validators.maxLength(255)]],
 | 
			
		||||
        edgeLicenseKey: ['', [Validators.required, Validators.maxLength(30)]],
 | 
			
		||||
        routingKey: this.fb.control({value: entity ? entity.routingKey : null, disabled: true}),
 | 
			
		||||
        secret: this.fb.control({value: entity ? entity.secret : null, disabled: true}),
 | 
			
		||||
        additionalInfo: this.fb.group(
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@
 | 
			
		||||
    <mat-toolbar class="mat-elevation-z1 tb-edit-toolbar mat-hue-3" fxLayoutGap="16px" fxLayoutGap.lt-xl="8px">
 | 
			
		||||
      <mat-form-field floatLabel="always" hideRequiredMarker class="tb-widget-title">
 | 
			
		||||
        <mat-label></mat-label>
 | 
			
		||||
        <input [disabled]="isReadOnly" matInput required
 | 
			
		||||
        <input [disabled]="isReadOnly" matInput required maxlength="255"
 | 
			
		||||
               [(ngModel)]="widget.widgetName" (ngModelChange)="isDirty = true"
 | 
			
		||||
               placeholder="{{ 'widget.title' | translate }}"/>
 | 
			
		||||
      </mat-form-field>
 | 
			
		||||
 | 
			
		||||
@ -158,6 +158,7 @@
 | 
			
		||||
        "user-lockout-notification-email": "In case user account lockout, send notification to email",
 | 
			
		||||
        "domain-name": "Domain name",
 | 
			
		||||
        "domain-name-unique": "Domain name and protocol need to unique.",
 | 
			
		||||
        "domain-name-max-length": "Domain name should be less than 256",
 | 
			
		||||
        "error-verification-url": "A domain name shouldn't contain symbols '/' and ':'. Example: thingsboard.io",
 | 
			
		||||
        "oauth2": {
 | 
			
		||||
          "access-token-uri": "Access token URI",
 | 
			
		||||
@ -174,21 +175,28 @@
 | 
			
		||||
          "client-authentication-method": "Client authentication method",
 | 
			
		||||
          "client-id": "Client ID",
 | 
			
		||||
          "client-id-required": "Client ID is required.",
 | 
			
		||||
          "client-id-max-length": "Client ID should be less than 256",
 | 
			
		||||
          "client-secret": "Client secret",
 | 
			
		||||
          "client-secret-required": "Client secret is required.",
 | 
			
		||||
          "client-secret-max-length": "Client secret should be less than 2049",
 | 
			
		||||
          "custom-setting": "Custom settings",
 | 
			
		||||
          "customer-name-pattern": "Customer name pattern",
 | 
			
		||||
          "customer-name-pattern-max-length": "Customer name pattern should be less than 256",
 | 
			
		||||
          "default-dashboard-name": "Default dashboard name",
 | 
			
		||||
          "default-dashboard-name-max-length": "Default dashboard name should be less than 256",
 | 
			
		||||
          "delete-domain-text": "Be careful, after the confirmation a domain and all provider data will be unavailable.",
 | 
			
		||||
          "delete-domain-title": "Are you sure you want to delete settings the domain '{{domainName}}'?",
 | 
			
		||||
          "delete-registration-text": "Be careful, after the confirmation a provider data will be unavailable.",
 | 
			
		||||
          "delete-registration-title": "Are you sure you want to delete the provider '{{name}}'?",
 | 
			
		||||
          "email-attribute-key": "Email attribute key",
 | 
			
		||||
          "email-attribute-key-required": "Email attribute key is required.",
 | 
			
		||||
          "email-attribute-key-max-length": "Email attribute key should be less than 32",
 | 
			
		||||
          "first-name-attribute-key": "First name attribute key",
 | 
			
		||||
          "first-name-attribute-key-max-length": "First name attribute key should be less than 32",
 | 
			
		||||
          "general": "General",
 | 
			
		||||
          "jwk-set-uri": "JSON Web Key URI",
 | 
			
		||||
          "last-name-attribute-key": "Last name attribute key",
 | 
			
		||||
          "last-name-attribute-key-max-length": "Last name attribute key should be less than 32",
 | 
			
		||||
          "login-button-icon": "Login button icon",
 | 
			
		||||
          "login-button-label": "Provider label",
 | 
			
		||||
          "login-button-label-placeholder": "Login with $(Provider label)",
 | 
			
		||||
@ -197,6 +205,7 @@
 | 
			
		||||
          "mapper": "Mapper",
 | 
			
		||||
          "new-domain": "New domain",
 | 
			
		||||
          "oauth2": "OAuth2",
 | 
			
		||||
          "password-max-length": "Password should be less than 256",
 | 
			
		||||
          "redirect-uri-template": "Redirect URI template",
 | 
			
		||||
          "copy-redirect-uri": "Copy redirect URI",
 | 
			
		||||
          "registration-id": "Registration ID",
 | 
			
		||||
@ -206,14 +215,17 @@
 | 
			
		||||
          "scope-required": "Scope is required.",
 | 
			
		||||
          "tenant-name-pattern": "Tenant name pattern",
 | 
			
		||||
          "tenant-name-pattern-required": "Tenant name pattern is required.",
 | 
			
		||||
          "tenant-name-pattern-max-length": "Tenant name pattern ishould be less than 256",
 | 
			
		||||
          "tenant-name-strategy": "Tenant name strategy",
 | 
			
		||||
          "type": "Mapper type",
 | 
			
		||||
          "uri-pattern-error": "Invalid URI format.",
 | 
			
		||||
          "url": "URL",
 | 
			
		||||
          "url-pattern": "Invalid URL format.",
 | 
			
		||||
          "url-required": "URL is required.",
 | 
			
		||||
          "url-max-length": "URL should be less than 256",
 | 
			
		||||
          "user-info-uri": "User info URI",
 | 
			
		||||
          "user-info-uri-required": "User info URI is required.",
 | 
			
		||||
          "username-max-length": "User name should be less than 256",
 | 
			
		||||
          "user-name-attribute-name": "User name attribute key",
 | 
			
		||||
          "user-name-attribute-name-required": "User name attribute key is required",
 | 
			
		||||
          "protocol": "Protocol",
 | 
			
		||||
@ -1448,9 +1460,11 @@
 | 
			
		||||
        "name-required": "Name is required.",
 | 
			
		||||
        "edge-license-key": "Edge License Key",
 | 
			
		||||
        "edge-license-key-required": "Edge License Key is required.",
 | 
			
		||||
        "edge-license-key-max-length": "Edge License Key should be less than 31",
 | 
			
		||||
        "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.",
 | 
			
		||||
        "cloud-endpoint": "Cloud Endpoint",
 | 
			
		||||
        "cloud-endpoint-required": "Cloud Endpoint is required.",
 | 
			
		||||
        "cloud-endpoint-max-length": "Cloud Endpoint should be less than 256",
 | 
			
		||||
        "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.",
 | 
			
		||||
        "description": "Description",
 | 
			
		||||
        "details": "Details",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user